PHPDevShell

Where would I typically use PHPDevShell?

PHPDevShell would typically be used to develop back-end systems for company processes. PHPDevShell is essentially a ready made GUI application where you can immediately start with the development work that matters most, your application. For security and usability it already provides you with user registration and management, role management, group management, access rights security, important system settings, templates, control panels, cronjob management, menu management, plugin management etc.

Please see Documentation for more information.

Official Documentation

This documentation is written for PHPDevShell V 3.x.x and newer.

The current documentation is simple and to the point, please feel free to contribute to this documentation.

Please continue reading our Introduction page. PHPDevShell uses the wonderful Launchpad System for team development collaboration. See the Launchpad Manual for instructions on using this tool.

  1. For download details and other technical information, please visit our page on Launchpad.
  2. Read the FAQ.
  3. Download PHPDevShell.
  4. Code Repository
  5. API Documentation
  6. Join the development team by following this process Join team.

Joining the PHPDevShell Development team

If you want to get in contact with us for any matter, whether its to become a plugin developer, talk to us or just get support. Please use the forms supplied on the website.

Installation

Requirements;

You need the following on your Apache server for PHPDevShell to work correctly.

  • PHP: Greater then PHP 5.2.x is minimum from PHPDevShell V 2.5.1.
  • PHPDevShell was developed on MySQL 5.0.x, I believe older versions above MySQL 4.1 should work fine.  (Multiple database support will be added soon).
  • Apache: PHPDevShell was developed on Apache 2.x, 1.x should work fine though.
Be careful! the first file (PHPDS-defaults.config.php) is part of the distribution and therefore will be overwritten on each update. You should not modify this file, as your changes will be lost. For a simple single site setup use the single-site.config.php.RENAME (remove .RENAME) file instead, or the site-specific config files (see below). single-site.config.php will always load when available.

Override default settings by copying settings from PHPDS-defaults.config.php to your own config file.

Installation of PHPDevShell is extremely easy;

  1. Extract the downloaded compressed file of the PHPDevShell package.
  2. Create a Mysql database (Character set : UTF-8 Unicode and Collation : utf8_general_ci) note the database name, username and password.
  3. Rename /config/single-site.config.php.RENAME to /config/single-site.config.php and change the settings.
  4. Upload the complete PHPDevShell directory to your web server including your modified setup files.
  5. Make the whole "write" and sub folders writable (if default else make custom folders writable).
  6. Browse to the http://localhost/other/service/service.php (directory where you uploaded PHPDevShell), a Database install script will appear, complete this as the final installation step.
  7. Follow instructions on database install script that loads when you open PHPDevShell in your browser...

Optional Cronjobs

Create cronjob to run [http://[install location]/index.php?m=742061208 every 10 minutes or lower (if automated file execution is required).

Optional Cache, Gzip, Friendly URLs

If your server support mod_rewrite and mod_expires you can rename rename.htaccess to .htaccess to gain cache, gzip performance and friendly urls. PS: Remember to switch friendly urls on in the gui to make it work.

Optional Multiple Domains

For multiple domains working from the same directory with different databases for each package, modify _host.config.php and add configuration files as required.

Upgrading

Always do a database and file backup before upgrading.

Most older plugins will still work on PHPDevShell v3, however we have provided a backwards compatible theme for your old plugins called oldlegacy2, so make sure your old plugin files are switched to this theme if they are not using a custom theme.

Be careful! the first file (PHPDS-defaults.config.php) is part of the distribution and therefore will be overwritten on each update. You should not modify this file, as your changes will be lost. For a simple single site setup use the single-site.config.php.RENAME (remove .RENAME) file instead, or the site-specific config files (see below). single-site.config.php will always load when available.

Override default settings by copying settings from PHPDS-defaults.config.php to your own custom config file.

  1. Extract and upload downloaded PHPDevShell package to your PHPDevShell install directory overwriting old content.
  2. Rename /config/single-site.config.php.RENAME to /config/single-site.config.php and change the settings.
  3. Execute /other/service/service.php from your installed PHPDEvShell directory.
  4. Complete the instructions in your browser.

* PS: If you have PHP notices turned on, you might see a notice until you ran the upgrade, ignore these notices.

Advanced Configuration

When PHPDevShell is executed, it reads various configuration files from the /config folder. These files holds configuration information, some are required, some are optional, most have good default values. No specific file is mandatory (i.e. you can store the information in any of them), but without the correct mandatory parameters (such as database access), PHPDevShell will not be able to run.

PHPDS-defaults.config.php

The idea is to take settings you would like to customize from config/PHPDS-defaults.config.php and copying it to your custom configuration file. This allows you to have a customized configuration set without updates overwriting your config.

The configuration files

Be careful! the first file (PHPDS-defaults.config.php) is part of the distribution and therefore will be overwritten on each update, it is there to reserve default settings. You should not modify this file, as your changes will be lost. For a simple single site setup use the single-site.config.php.RENAME (remove .RENAME) file instead, or the site-specific config files (see below). single-site.config.php will always load when available.

Override default settings by copying settings from PHPDS-defaults.config.php to your own config file.

For instance if I want to set my site in production mode, I would copy from PHPDS-defaults.config.php into my own or single-site.config.php config file;

$configuration['production'] = true;

This will now override the default.

  • multi-host.config.php.UNUSED (Remove .UNUSED for multiple install instances)
multi-host.config.php can contain multiple arrays of host names.

This file is meant to store a list of config files which are not used by default. For example:

$configuration['host'][$_SERVER['SERVER_NAME']] = 'mysite';

$_SERVER['SERVER_NAME'] can be replaced with your domain name that requires own instance of installation, for instance;

$configuration['host']['somesite.com'] = 'mysite';

With your file created like this;

  • mysite.config.php

Note that you can add any configuration information here, also we advise you put them inside other configuration files and only keep this array in this file.

This file contains all the default values for the required parameters. Make sure you have at least one file with values for all these parameters.

  • More "site specific" files are loaded (if they exist): one with the content of $configuration['host'][$_SERVER['SERVER_NAME']] (usually set in the multi-host.config.php file), one with $_SERVER['SERVER_NAME'] (all with the suffix .config.php, in the /config folder).

This means that even without changing anything in multi-host.config.php, hosting on a domain called "www.mysite.com" will automatically try to include www.mysite.com.config.php

NOTES

  1. all theses files are tried in this order, so if a parameter is set several times, only the last will be used.
  2. the server name comes from Apache web server, so if you use virtual hosting, you can have the same installation of PHPDevShell running on several hostnames with (partially or completely) different configuration; this is also true for development/production servers.
  3. each file is included only ONCE; if a file is asked a second time nothing happens
All files, successful or not, will be listed in the configuration variables $configuration['config_files_used'] and $configuration['config_files_missing'], so in case of problem you can detect if a file is missing or not.

Example

For the site "www.phpdevshell.org", let's pretend:

  • $_SERVER['SERVER_NAME'] is "www.phpdevshell.org"
  • in the host file, $configuration['host']['www.phpdevshell.org'] is set to "main-site"

all these files will be tried, in order:

  1. multi-host.config.php
  2. PHPDS-defaults.config.php
  3. single-site.config.php
  4. main-site.config.php
  5. www.phpdevshell.org.config.php

with this mecanism, you can have, for each site, a different file for the production server and the development server.

Default mandatory parameters

In order to run, PHPDevShell needs access to the database. You can use a plain install (no manual changes) if you set your database with the following values:

  • database host: localhost
  • database name: phpdev
  • database user login: phpdev
  • database user password: phpdev

Of course on a production server you MUST change at least the password.

Cronjob Manager

Scheduled maintenance

Scheduling maintenance as a task is more flexible than manual maintenance. You can start the maintenance script by:

  • executing a locally run script
  • calling the script url on the web server

Scheduling tasks on Linux and Unix

Remember your menu item [742061208] System Cronjob must have guest access otherwise the server will not be able to execute and run the cron system.

On Linux, BSD, Solaris or MacOSX you can use the cron daemon to automatically run the maintenance script. In most cases you will need shell access to your server to add cron tasks, using the following commands:

  1. Log on to your server, then use the following command to export your existing scheduled tasks to a text file: crontab -l > cron.txt
  2. In a text editor, add one of the following entries to the cron.txt file:

If your server supports:

#If your server supports wget:
*/10 * * * * wget \-q \-O /dev/null [http://www.example.com/index.php?m=742061208]
#If your server supports fetch:
*/10 * * * * fetch \-o /dev/null [http://www.example.com/index.php?m=742061208]
#If your server supports lynx:
*/10 * * * * lynx > /dev/null \-dump [http://www.example.com/index.php?m=742061208]

When you have finished editing the cron.txt file, use the following command to import it into the cron daemon:

crontab cron.txt
You can now continue to manage cronjobs inside PHPDevShells cronjob manager UI. The UI allows you to set a controller to execute on any given date or time interval. a Cronjob is developed just like any other controller, you just need to set is as cronjob in Menu Configuration - Node Type.

Installing Plugins

Installing plugins is generally the same for all third party plugins, except where the developer of a plugin explicitly gave additional information.

You obviously need PHPDevShell up and running first (see installation instruction).

Follow these steps to install plugins;

  1. Download plugin.
  2. Extract plugin from archive, the extracted plugin folder will have a clean name after extraction like "MyPlugin" with all plugin files and folder inside that folder.
  3. Copy the complete extracted plugin folder to the /plugins/ folder within your PHPDevShell installation directory.
  4. Login to your PHPDevShell installation and browse to System Management -> System Admin -> Plugins Admin.
  5. Click on Install Plugin next to the plugin you just downloaded and copied.

That's it! New menus and/or database tables get created during installation and this is basically all you have to do to install a new plugin. You might need to follow additional instructions from the plugin author.

Setting up a Cronjob

Scheduled maintenance

Scheduling maintenance as a task is more flexible than manual maintenance. You can start the maintenance script by:

  • executing a locally run script
  • calling the script url on the web server

Scheduling tasks on Linux and Unix

Remember you menu item [742061208] System Cronjob must have guest access otherwise the server will not be able to touch the cron system.

On Linux, BSD, Solaris or MacOSX you can use the cron daemon to automatically run the maintenance script. In most cases you will need shell access to your server to add cron tasks, using the following commands:

  1. Log on to your server, then use the following command to export your existing scheduled tasks to a text file: crontab -l > cron.txt
  2. In a text editor, add one of the following entries to the cron.txt file:

Choose the command your server supports, we will set the cron to run every two minutes:

#If your server supports wget:
*/2 * * * * wget \-q \-O /dev/null [http://www.example.com/index.php?m=742061208]

#If your server supports fetch:
*/2 * * * * fetch \-o /dev/null [http://www.example.com/index.php?m=742061208]

#If your server supports lynx:
*/2 * * * * lynx > /dev/null \-dump [http://www.example.com/index.php?m=742061208]

When you have finished editing the cron.txt file, use the following command to import it into the cron daemon:

		crontab cron.txt

You can now continue to manage cronjobs inside PHPDevShells cronjob manager ui.

About “.local” files and override possibilities

Depending on how "low" you want to override, you have basically two possibilities. The first one, class aliasing, is for general use, where the second file, file override, is only some particular situations.

Using class aliasing

Class aliasing is a simple yet very powerful mecanism. It basically allows you to subsitute any class with another one when instanciated with the Factory. This substituion doesn't occur at the level of the PHP interpretor, so it avoids circularity problems.

Let's say for example, you want to override only one method of the PHPDS_core class. First you declare your class alias in the plugin.config.xml/ file:

	

then you define your override class in the file includes/MY_core.class.php:

class MY_core extends PHPDS_core
{
	public function formatTimeDate ($time_stamp, $format_type_or_custom = 'default', $custom_timezone = false)
	{
		/* do my stuff */
	}
}

That's it. Don't forget the re-install your plugin the first time, so the database is updated with the class alias declaration.

General File Overriding

This is quite an advanced feature and will not be used by many. Only where core system files are heavily modified this should be opted for. In general there are more elegant ways of overriding objects.

In several places we tried to use ".local" files to allow you to override the default system files as much (where needed). For example, the starter, "index.php" (in the main phpdevshell directory) which is meant to instantiate and run the main instance of PHPDevShell, would give control to "index.local.php" if it is present. Here is a list of files that can be overridden with a .local file:

  • index.php will give control to "index.local.php" if present (only wrapped in a very simple try/catch
  • with every config file that is tried, a ".local" version will be tried as well (using "require_once()").

Overriding from the top

You can easily use a custom version of any of the Core Set by providing it in the appropriate method. For example, using "index.local.php" an alternate Skel:

require 'includes/PHPDS.inc.php';
require 'advanced_policy.php'; // the custom security object is defined here

class custom_PHPDS extends PHPDS
{
	public function PHPDS_security ()
	{
		if (empty($this->security)) {
			$this->security = new custom_security();
			$this->security->PHPDS_dependance($this);
		}
		return $this->security;
	}
}

$PHPDS = new custom_PHPDS;
$PHPDS->run();

The custom_security class is a daughter of PHPDS_security class with a little tweak, and now PHPDS is using it instead of the default security class. You can of course use this class method to provide parameters to your security instance if needed.

Introduction

Where would I typically use PHPDevShell?

PHPDevShell can be used to develop web orientated system, from websites to business applications. PHPDevShell ships with most common used GUI functionality found in applications and websites. This head start in development allows you to immediately focus on the problem at hand. For security and usability it already provides you with user registration and management, role management, group management, access rights security, system settings, powerful theming system, control panel, cronjob management, menu management, plugin management, tagging management, CRUD, ORM, etc.

Why would I want to use PHPDevShell?

PHPDevShell provides the ability for you to get a php application/website up and running in an incredibly short amount of time. But what about resource usage and execution speed? PHPDevShell's default memory usage is half that of Wordpress which is also considered "lightweight" in the world of PHP. PHPDevShell never forces you in a particular design philosophy either. There is no doubt that there are incredible frameworks out there, we respect all of them and acknowledge them. However, PHPDevShell is designed to perform where little development time/resources is available.

PHPDevShellFlow.png

Final Word

Simply install PHPDevShell as per instructions, write a PHP script as you would normally using clean php, add it to the menu and you have a full blown system behind it handling everything for you. You will impress your clients having an available preview of the base within hours.

Unlike most other frameworks PHPDevShell does not generate any kind of code. No terminal commands have to be executed either. There are several reason why we opted out of code generation, mostly because a newcomer feels lost and things could get messy or complicated quickly. The cleanest way of coding is by doing things by hand.

PHPDevShell is an Open Source (GNU/LGPL) PHP Rapid Application Development framework, aimed at developing any web based application, where Speed, Security, Stability ,Modularity and Flexibility are all essential. It is designed keeping a very easy learning curve in mind. What makes PHPDevShell unique is the fact that if you know PHP, you can write your application inside PHPDevShell in minutes without learning a new “language” so to speak.

Wrapped in a complete HTML5 default theme, PHPDevShell provides;

* End-User system (Advance Registration, Login, Logs, Profile Management).

* Advanced Administration system.
* Cronjob system
* Templating system
* Unlimited level security system (Groups and Roles)
* Plugin System (To develop plugins for PHPDevShell)
* Logs System (Multiple Levels)
* Notice Objects
* Search Engine
* Help Engine
* Menu System
* Tagging System
* Class Registry System
* Easy Forms (CRUD)
* ORM
* And many more…

PHPDevShell release versioning follows a numerical convention comprised of three numbers: Major, Minor and Maintenance. The version is presented in the major.minor[.maintenance] format.

Major Release Number (X.1.1)

An increment of the major number generally indicates a major rework or rewrite of the code base.

May be completely incompatible with prior major releases.

Minor Release Number (1.X.1)

An increment of the minor number usually indicates a significant change to functionality or architecture.

Moderate to high level of backward compatibility with previous minor increments.

Maintenance Release Number (1.1.X)

An increment of the maintenance number usually indicates bug fixing within the minor release and possibly small enhancements and limited new features.

Thank you for trying out PHPDevShell, please ask question and help write documentation. This is all that is asked in return for saving you months of work.

Getting Started

Note: This is just a tutorial, PHPDevShell offers advanced methods while coding. To keep this tutorial simple we used standard PHP code. In this tutorial, we'll use the folder phpdev as the folder where your installation of PHPDevShell resides (if you have not installed yet, follow this link). Change it to fit your actual folder name.

Starting with a new framework is always both a thrill and daunting task. You might feel what you have in front of you is interesting, but how can you get started with the most basics and work with it in its simplest form?

The getting started guide only focuses to get you started on what you are familiar with. For this tutorial we don't want to confuse you with new methods and methodologies, we just want to get you started using "good ol' PHP" for now :). We will guide you and give you a glimpse of why PHPDevShell is ready to get you going in 5 Minutes!

Screenshot_2.png

To follow this tutorial, you'll need:

  • a working installation of PHPDevShell (I'll assume it's accessible at http://localhost/phpdev/)
  • an administrator account (a registration on this installation allowing you to manage plugins)
  • a text editor or IDE to code in (we recommend free NetBeans or free Eclipse),
  • you must also have access to create folders upload files to your installation.

Now lets pick a name for your plugin. In PHPDevShell, every script belongs to a plugin. This allows you to stay consistent within a structure and have a whole site inside a single plugin folder. Let's say you name your plugin "tutorial" (all lowercase). Create a folder with this name inside the "/phpdev/plugins/" folder, e.g. "/phpdev/plugins/tutorial"

Hello world

Of course our first script will be the classic (yet fancier) hello world application, using only code you are familiar with.

Once you have created the tutorial folder in "phpdev/plugins/", you will be placing your new script (lets call it first.php for now) "first.php" inside it. Open/create your first.php script with your favorite PHP editor and start writing code, lets do the promised hello world:

<?php
	// Hello world heading.
	echo "

Hello, world!

"; // But don't feel limited. You have access to absolutely all standard PHP functions, even queries: $query = mysql_query('SELECT * FROM pds_core_users WHERE user_id = 1'); // Or use object queries if Models are not going to be used. $query = $db->newQuery('SELECT * FROM pds_core_users WHERE user_id = 1'); // You can build HTML right here. $html = "

Some HTML

"; echo $html; ?>
Every execution point in PHPDevShell is connected from the menu system as a node. The menu system has node types, this can be seen as an execution point for simple scripts, plugins, websites, widgets, ajax etc. For instance a widget node type can be called from inside a controller node type. This means that when this controller node type is access, the widget inside that node will also be loaded if the widgets node permission allows it.

Adding a menu to open your script in your Browser

The most common way of executing a script is to create a menu item. Creating a menu in PHPDevShell makes an item appears in the dashboard which when clicked will run your script. Go to System Management -> Menu Admin -> New Menu -> Fill the fields in as:

Plugin Item: Standard Plugin (leave rest of left column blank).

Name: The display name visible to the user ; let's try "Hello, World!" (note that this name can be different based on the user's language)

Alias: an alternate name, which can be used from the code to refer to the menu whatever the user's language ; let's try "hello-world"

Plugin Name/Folder: the name of the folder our script reside in; we chose "tutorial" earlier

URL or path: this will tell the engine where exactly to find our script file ; it's "first.php"

Leave the rest to their default values and click "Save". Then click on "Dashboard" in the upper menu: our new menu item "Hello, World!" should appear.

Screenshot-2_0.png

First run

OK we're ready! click on the menu icon "Hello, World!" and you should see your nice greeting. Basically any PHP script can be run that way; we call this "legacy mode" because it's way scripts where written before version 3.0.

But anyone can see my script :(

Ah, so you want to quickly protect your secretive Hello World with some magic using the Access Control? That is not a problem, simply add this line at the top of your script:

	// Stop people from seeing my highly secretive menu.
	(is_object($this->security)) ? $this->security->securityIni() : exit('Access Denied!');

Now remember to give your menu item the right permissions using by going to System Management->Policy Admin->Access Control

Sample Plugin

PHPDevShell is packaged with a sample plugin called ExamplePlugin, be sure to have a look at it in order to get started as quickly as possible with more advanced coding techniques.

Plugins

heading_Screenshot-3_0.png

Plugins play an extremely important role when you decide to create a project using PHPDevShell. Plugins are the main location of your source code for your project. We provide a fairly solid and stable MVC structure for your plugins, however, you are free to follow your own (if any MVC is even required).

You will use your plugin as the main folder of your projects, your project may have multiple plugins, with multiple shared classes per plugin. To highlight some point of what a plugin Can be:

  • a Small script that executes when a connected menu is accessed.
  • a Set of classes that can be created instances.
  • a Complete complex application with a totally different look and feel.
  • a Complete complex website with a totally different look and feel.
*PHPDevShell resources is a powerful set of objects that contain not only useful methods but also full sets of data on just about anything about the current user, system settings and much more. Resources can be shared with anything inside a plugin (classes, models, controllers etc).

PHPDevShell Plugins support both upgrades and install through an easy to read XML base;

  • Automatic Upgrades
  • Version Checking
  • Menus
  • Settings Installation
  • Class Registration
  • Dependency Checking
  • Installing, Upgrading, Uninstalling

Introduction to Plugins

All plugins are located in the plugins folder of PHPDevShell. In general anything can be done in your own plugins folder from APIs to UIs. If you are used to one standard that is fine, use your own standards. If you want to use APIs from another framework, that is very possible too. First thing to do is to give your plugin a name. This is done by creating a folder in the PHPDevShells plugin folder. Call it whatever you like. You can follow this tutorial by opening ExamplePlugin found in the standard PHPDevShell distribution folder.

Looking at the folders and files

config

In general, you can use the config folder to contain any type of configuration files your project might require, this can be in any format or form. The config folder also contains the most important file for PHPDevShell to understand your plugin. This XML file is name plugin.config.xml. This simple yet very advanced makes your plugin both installable by the plugin manager and upgradeable.

controllers

Controllers contains... erm controllers. But not necessarily in an MVC fashion. Controllers can also be plain php scripts. It does not matter, it is up to the coder to decide what he will use. Controller is also the starting point for a menu item to be executed. If a plugin menu is selected it will load a controller file located herein. Controllers can be named general descriptive names with my-example.php being a perfect example.

The controller can contain a simple script as:

<?php
	print "Hello World";
?>

images

The image folder will contain your plugins logo (logo.png) and controller icons and any general graphics. The logo.png is read automatically for each plugin, so is the controller icons. The controller icons (48x48px) needs to be named the same as the controller file. In this case my-example.png. This will give the user interface an elegant flow.

includes

The includes folder contains helper and registered public classes. Please read all about plugin.config.xml about registering and sharing classes. a Class always has ".class.php" at the end of the file. a Class name is also named the same as the file name. Looking at the example supportExample.class.php under includes:

class supportExample extends PHPDS_dependant
{
	/**
	 * The cool thing about supporting/helper classes is the ability to reuse code over and over again,
	 * PHPDevShell offers and extemely easy way to do this.
	 * Extend with PHPDS_dependant to share core object from PHPDevShell.
	 */

	/**
	 * Properties are used like normal.
	 * @var string
	 */
	public $somevar = 'Hello World';

	/**
	 * This just shows that you can reuse html too, this is ethically correct.
	 * @return string
	 */
	public function someReusedMethodwithHTML()
	{
		// Well what do you know, we have access to all PHPDevShell object.
		$name = $this->configuration['user_display_name'];
		$date = $this->core->formatTimeDate(time());

		// Obviously this method can be extended, but lets keep it simple.
		return sprintf('
			
				Hi %s, as you can see, this can now be reused. Todays date is %s.
			', $name, $date);
	}

	/**
	 * Support classes can have their own models. Models will be looked for in the models folder,
	 * with relation to the class file name, in this case it will be models/supportExample.query.php
	 * @return string
	 */
	public function someReusedModel()
	{
		// Obviously this method can be extended, but lets keep it simple.
		// Calling a model from within a support class.
		return $this->db->invokeQuery('ExamplePlugin_someExampleQuery');
	}
}

As soon as this class is registered after install, any plugin will be able to call it with:

		$helper = $this->factory('supportExample');
		$helper->someReusedMethodwithHTML();
		$helper->someReusedModel();
Did you know classes can also have models. Just like normal controller models, class models sits in the root of the models folder. In this case, this model would have been called models/supportExample.query.php. This allows you to split model relation to a separate file.

language

The language folder is there to serve your plugins translation needs. It houses different files to make translation possible. The first file to focus on is gettext.lang.php, this file has no real purpose other then making xgettex extract english strings for menus out of it for translation. So if you want to be able to translate your menus, make sure this file is there with all your default installed menu names. It would typically look like this.

<?php
_('Menu Name');
?>

Files that are more important are the .pot and compiled .mo files which contains actual translation.

models

The models folder contains query and data models for your controllers and helper classes. a Model contains multiple classes inside one file. Files are named in relation to the controller. In this example it would be models/my-example.query.php. Its should be noted that if the controller is in sub folders, the same folder should be followed for its model counterpart. Models generally contains content like this.

class ExamplePlugin_someExampleQuery extends PHPDS_query
{
	protected $sql = "
		SELECT
			example_name
		FROM
			_db_ExamplePlugin_example
    ";
	protected $singleValue = true;
}

views

The view holds your typical view for the controller. Views are also named in accordance with its controller spouse. In this example it would be view/my-example.tpl. The view may be used in conjuntion with a template engine. You are free to use clean php views or a template engine of your choice.

Conclusion

Following this article and its informative links should give you a much better understanding of how PHPDevShell plugins work. If you feel something is missing, comment here and it will be added.

Example Plugin

article_example.jpg

If you are anything like me, you would want examples to just get started. PHPDevShell ships with a neat ExamplePlugin that shows you exactly how to get the ball rolling in a matter of minutes. It should be remembered that PHPDevShell will in no way dictate how you work, it does offer you guidelines on how some things could be done to organize your work in a neat way.

Following by example is most often the simples and most affective form of learning.

Screenshot - 23122011 - 10:06:36.png

After installing PHPDevShell simply go to System Management->Plugins Admin, next to ExamplePlugin hit the install option. After the Example Plugin installation you will find a new set of menus in the root navigation. Here you will find results of coding examples and a sandbox to play around with. You can also view the plugins code under plugins/ExamplePlugin.

Plugin Config File

plugin.config.xml

The plugin config file is the most important part of your plugin for PHPDevShell to understand what it is your plugin should install. This powerful yet simple to use system allows both installing and upgrading of plugins, it even supports automated upgrades (downloads and installs without any uploading from your side). Your config file consists of standard XML;

<?xml version="1.0" encoding="UTF-8" ?>

Plugin XML code here

In the end, your plugin will have depending on your needs the following structure;

<?xml version="1.0" encoding="UTF-8" ?>

	
	
	
	
	
	
	
	
	
	
	
	http://version.phpdevshell.org/ExamplePlugin.xml
	
		
			
		
		
			
		
		
			
		
		
			
			
		
		
			
		
	
	
		
			
		
		
			
		
		
			
		
	
	
		
	

Don't copy above xml though, use the EmptyPlugin provided with the PHPDevShell distribution package to get a foundation on your xml.

Basic plugin information

The first group of information is about your plugin. This information is previewed in the plugin manager information page for each plugin. Quite simple and self explanatory. With this information your plugin will be fully readable and will display correctly in the plugin manager.

	continue from inside the config tags

	Use a proper plugin name without using special characters (same as folder).
	ExamplePlugin

	Human readable version number of your plugin.
	3.0.0

	a Short description of your plugin (what does it do?).
	This plugin is purely meant for examples on how to use PHPDevShell.

	If this plugin is based on another and only modification by you, place the original authors names here.
	Jason Schoeman

	Name of the developer/modifier for this plugin.
	Jason Schoeman

	Email address of the developer for this plugin.
	titan@phpdevshell.org

	Plugin developers web address.
	http://www.phpdevshell.org

	Date the plugin was developed or updated.
	10 November 2010

	Copyright notice you would like to amend to your plugin.
	Copyright 2009 PHPDevShell.org All rights reserved.

	License this plugin is released under.
	http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html GNU/LGPL

	Detailed information and help for this plugin.
	
			

See some features and getting started with plugins. This plugin can actually be installed and is working. It will install a few simple menus, hooks, queries and settings serving you with the basics in writing a plugin.

The rest of the config follows here...

Checking plugin for updates

The plugin manager offers a simple yet effective way for a admin to check for updates for your plugin. It even support automatic upgrading of plugins if this configuration is completed and the file is hosted somewhere.

Now, looking at how the next line of XML looks, this simply shows that we are showing the plugin manager where to look for XML file for possible hosts.

	Code Version XML URL check.
	Version (current) below is used to check for new releases and has little to do with database version.
	See end of file to learn how the upgrade xml should look.
	http://version.phpdevshell.org/ExamplePlugin.xml
Note the "current" property, this is purely a number based on nothing more the to check if a version is newer. This number has nothing to do with the plugins version. It purely means that if your first plugin current="10" your next plugin release can be name current="11". This will allow the system to return true if there is an update.

Looking at the update xml file

The update xml is quite simple. It is hosted publicly so that the plugin manager can find it. The XML file is named the same as what you used in your versionurl property.

<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>

	Check against version number and human readable version number of plugin.
	3.0.1-Stable
	The URL where the latest version can be downloaded.
	http://version.phpdevshell.org/ExamplePlugin.zip
	Release note regarding plugin update.
	This is just an example remote upgrade.

This is all that needs to be presented in the update XML file. Now if versionurl current property is lower then update xml version current property then the update will trigger "true" in the sense that an update is available. The update will then download from the download url tag. This will in turn trigger the FTP update and attempt to update the whole system.

For the auto upgrade to be a success, FTP must be setup correctly and the ZIP must contain the root folder of the plugin. So if extracted, it will extract as per example to folder ExamplePlugin.

This covers the basics of how a config should look. We will however continue to install menus, api registration and more with the rest of the xml file.

Installing plugin resources

The plugin resources install is all wrapped under one parent tag, which handles the installation of:

  • Database Queries
  • Dependencies
  • Settings
  • Menus
  • Supporting Class APIs

Lets look at the parent tag. You will note the version property. PHPDevShell plugins have two version sets for each plugin. Software version and Database version. They are unrelated, software version focuses on source code changes where the database version focuses on actual data changes contained within the database. So the version property of the install tag will always be updated as soon as a new version of a plugin is released only when there are database changes. Else simply the general informations version tag needs to be changed.

The version property will also tell the upgrade tags to stop when it reaches the value of this number. Meaning each database upgrade tag will be executed while the upgrade tag version is smaller then the install tag version.

It is very important to note that the install tag will always contain the most up to date changes in it. Meaning. Don't just rely on the upgrade part which we will discuss later. Make sure the menus, settings and queries are all up to date in the install tag.

So above discussed will look like this:

	Version here represents the database version that should be install.
	If your database version needs no update, this number can stay the same.
	Upgrades further down will only be executed up to this number.
	This is the current database version that will be installed.
	Below is a series of example upgrade procedures.
	
		Most up to date install tags here...
	

Install Tag: dependency

The install tag tells the reports what other plugin classes this plugin depends on. It does not play a vital role, except to help prevent broken installation when a required plugin is not installed yet.

		[contains][All query, menu, dependencies, settings installation tags.]
		[param][class][string][mandatory][The class call it depends on, the call must be registered.]
		[param][plugin][string][mandatory][The default plugin the class is available in, it can be override with another class with the same call.]
		[note][This is how the plugin manager will know to what version upgrade scripts should be executed.]
		[note][Always keep install maintained to the latest menu, query, hooks and setting versions.]
		
			
			
			
			
			
		

Install Tag: queries

These tags are used to run multiple database queries. Each query is placed in its own queries->query tag. The installer will run them in order and will execute then in series. You have complete freedom in running any query you like.

Typical queries as per the example will look like this (remember that install->queries should contain the plugins most recent and complete sql):


			[contains][All install queries to create the tables for your plugin.]
			[note][If there is a SQL query error, the plugin will not install.]
			
				[string][This is a single actual sql query that will be executed]
				[note][SQL query be placed in [CDATA tags.]
				
					CREATE TABLE `_db_ExamplePlugin_example` (
						`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
						`example_name` varchar(255) DEFAULT NULL,
						`example_note` tinytext,
						`alias` varchar(255) DEFAULT NULL,
						PRIMARY KEY (`id`),
						KEY `index` (`alias`)
					) ENGINE=InnoDB DEFAULT CHARSET=utf8;
			
			
				[note][SQL queries can be repeated.]

					CREATE TABLE `_db_examplepluginanothertable` (
						`id` int(20) unsigned NOT NULL auto_increment,
						`user_id` int(20) default NULL,
						`message` varchar(255) default NULL,
						PRIMARY KEY  (`id`)
					) ENGINE=InnoDB DEFAULT CHARSET=utf8;
			
			
				[note][SQL queries can be duplicated.]
					REPLACE INTO `_db_ExamplePlugin_example` VALUES ('1', 'Toyota', 'Reliable Toyota', 'car');
			
			
				[note][SQL queries can be duplicated.]
					REPLACE INTO `_db_ExamplePlugin_example` VALUES ('2', 'Mazda', 'Legend Mazda', 'car');
			
			
				[note][SQL queries can be duplicated.]
					REPLACE INTO `_db_ExamplePlugin_example` VALUES ('3', 'Ford', 'Muscle Ford', 'car');
			
			
				[note][SQL queries can be duplicated.]
					REPLACE INTO `_db_ExamplePlugin_example` VALUES ('4', 'Citroen', 'French Citroen', 'car');
			
			
				[note][SQL queries can be duplicated.]
					REPLACE INTO `_db_ExamplePlugin_example` VALUES ('5', 'Mercedes', 'Quality Mercedes', 'car');
			
		

Install Tag: settings

PHPDevShell has a nifty settings repository to quickly and with a low footprint get an array of settings for a particular task. The Config Manager allows end users to easily change these settings. To add important settings to the database you can simply do:


			[contains][Contains tags of all settings that needs to be installed to the general PHPDevShell settings database.]
			
				This is a sample setting (1)!
				[string][Actual value of setting that needs to be stored.]
				[param][write][string][mandatory][The parameter id of the value to be stored.]
			
			
				Yet another sample setting (2)!
				[note][Setting tag needs to be repeated for each setting.]
			
			
				true
				[note][Setting tag needs to be repeated for each setting.]
			
		

The write property is the key of the setting, the content of the tags are the values. Getting settings in code is quite easy.

		// Looking at a simple $this->db method.
		// Lets get sample ExamplePlugin settings that we added during the install.
		$setting = $this->db->getSettings(array('sampleSetting1', 'sampleSetting2'), 'ExamplePlugin');

Install Tag: menus

We tried to make menu installation as simple as possible. Using the depth of the tags to structure the menus, because of this menus in the xml is easy to setup and very readable. The menu xml has multiple properties to adjust, however it works in its simplest form.

		
			
			[contains][All types of menu items that needs to be installed.]
			[note][Tags inside menus can be nested and repeated.]
			[note][Here we link to an existing menu item from another plugin, in this case the control-panel aka dashboard.]
			
			
				
				[contains][Menu items can be contained in itself, this will create a menu tree.]
				[param][name][string][not-mandatory][The name of the menu item, if empty the pluginName.menu.lang.php will be used.]
				[param][type][int][not-mandatory][There are 8 menu types, 1 is the default if left empty.]
								[1][Plugin script] normal plugin menu item in your plugin folder.
								[2][Link existing menu] item while staying in its own menu group when clicked.
								[3][Link existing menu] item while jumping to original scripts menu group when clicked.
								[4][External file] Include external PHP web applications into PHPDevShell.
								[5][HTTP URL] Normal url to outside web.
								[6][Empty Place Holder] This item will only serve as a unclickable menu place holder.
								[7][iFrame] Link url to both external url or onsite url.
								[8][Cronjob Menu Type] The same as a plugin script but is set as cronjob.
				[param][link][string][mandatory][The url, controller location or symlink holder will be entered here depending on type.]
				[param][hide][int][not-mandatory][There are 4 hide types, 0 is the default if left empty.]
								[0] Do not hide menu item.
								[1] Hide menu item from both Menu System and Control Panel.
								[2] Hide menu item from Control Panel only.
								[3] Hide menu item from Menu System only.
								[4] Hide menu only when inactive.
				[param][rank][string][not-mandatory][If you want to ensure ranking positions, will auto rank if left empty.]
								[int] Can be ranked with integer.
								[last] Will be ranked last in a menu group.
								[first] Will be ranked first in a menu group.
				[param][newwindow][int][not-mandatory][To make item open in new window set to 1, will not open in new if left empty.]
				[param][plugin][string][not-mandatory][Plugin name, use to install menu item to a different plugins menu structure.]
				[param][alias][string][not-mandatory][When switching on friendly urls in the settings and renaming rename.htaccess in the root, sef url will use this alias.]
				[param][parentlink][string][not-mandatory][Use with [param][plugin] or without to install in different structure.]
				[param][symlink][string][not-mandatory][Url or location with [param][plugin] or without to link to another menu item duplicating its use.]
								[note][Symlink is mandatory for menu types 1,2,6]
				[param][template][string][not-mandatory][Set template to use with plugin, if template is unavailable it will be installed.]
				[param][template][string][not-mandatory][Set the height of an iframe menu type.]
								[note][Height is mandatory for menu types 7]
				[param][layout][string][not-mandatory][Set a custom template.tpl location for a certain script.]
				[param][noautopermission][int][not-mandatory][Set to 1 to not add the installer of the plugin to permit menu item access.]
				-
				
					[note][Can be nested to present a deeper menu tree.]
					
					
					
					
					
						
							[note][Can be repeated.]
							
						
					
				
				[note][New menus on upgrades should be added here, this will always be the model on upgrades.]
				[note][Let us add some other odd menu types for examples sake as well.]
				
				[note][Installing a menu item to another plugins menu structure is easy as well.]
				
			
		
You should note that when using the link property, this is the location of the controller file under the controllers folder. This does not need to be a controller as such. It can be a plain php too.

Install Tag: classes

Registering classes plays a very important role. This makes the classes publicly accessible to other plugins. Not only that, these plugin calls can be override by other higher priority classes to manipulate original shared plugin class functionality. All classes registered with the classes tags should be available in the plugins includes folder and named something like exampleClass.class.php, the shared class must have this structure in order for it to share PHPDevShell resources:

class supportExample extends PHPDS_dependant
{
	// Normal class code here.
}

Plugins can then call a class using:

		$helper = $this->factory('supportExample');
		$helper->someReusedMethodwithHTML();
		$helper->someReusedModel();

To register a class, simply add the following xml, you can have multiple class tags to register multiple classes per plugin.

		[contains][Register classes to be publicly available and shared. There can be multiple classes of the same name in different plugins, they can overwrite each other by rank.]
		[note][Class name must be the same as what the file is called, groupTree.class.php and must be containded within includes folder.]
		[param][name][string][mandatory][The name of the class to be called by factory('groupTree') and the filename that contains the class groupTree.class.php]
		[param][alias][string][mandatory][Alternative callname for factory('PHPDS_groupTree'), will call same class though.]
		[param][plugin][string][mandatory][The plugin name/folder where the class belongs to.]
		[param][rank][mixed][not-mandatory][The lower in integer the rank the higher the chance is that this class will overwrite classes of the same names.]
			   [int] Can be ranked with integer.
			   [last] Will be ranked last and any preceding class will overwrite it.
               [first] Will be ranked first and will be overwriting lower ranked classes.
		
		
			
			
		

The upgrade tag

All plugins receive updates. An update is easy to accomplish if there are no data changes. PHPDevShell makes it just as easy to do data upgrade. You can easy database upgrades for multiple versions behind. Meaning, if a user only updates after 5 versions later, the system will run all datbase queries from the first version. This is very usefull to keep the upgrading process flawless.

The upgrade tag supports lesser functions then the install tag, seeing that allot of updates are taken from the install tag rather. So there is no need to design an upgrade for everything. As long as your install tag is up to date, allot of the updates will be taken from there.

Looking at the simplicity of the upgrade tag, you will with this example clearly see what it is capable of. Again, upgrades will run until the version is matched with the install versions tag;

	Upgrades only upgrade to the point where  is set.
	To preview how upgrade works set  to 2801 or higher.
	
		[contains][All query, menu, settings upgrade tags.]
		[param][version][int][mandatory][This upgrade version, will upgrade until install[version] is met.]
		[note][This is how the plugin manager will know to what version upgrade scripts should be executed.]
		
			[contains][All upgrade queries to create the tables for your plugin.]
			
				[string][This is a single actual sql query that will be executed on upgrade.]
				[note][SQL query be placed in  tags.]
				ALTER TABLE `_db_examplepluginanothertable` MODIFY COLUMN `user_id` int(40) unsigned NOT NULL;
			
			
				[note][The query tag should be repeated for each upgrade query.]
				ALTER TABLE `_db_examplepluginanothertable` MODIFY COLUMN `message` varchar(200) default NULL;
			
		
		
			[contains][Contains tags of all settings that needs to be upgraded or removed from the general PHPDevShell settings database.]
			
				More Settings (3)
				[string][Actual value of setting that needs to be stored.]
				[param][write][string][mandatory][The parameter id of the value to be stored.]
			
			
				We can do this all day long (4)
				[note][Setting tag needs to be repeated for each setting added.]
			
			
				[note][Setting tag needs to be repeated for each setting to be deleted.]
			
			
				[contains][Nothing, works from parameters only.]
				[param][delete][string][mandatory][The parameter id of the value to be deleted.]
			
		
		
			[contains][All menus that should be deleted.]
			[note][To add menus on upgrades the master install must always be modified with latest menus.]
			[note][Old menus should still be deleted even though they have been removed from install.]
			
				[contains][Nothing, works from parameters only.]
				[param][delete][string][mandatory][The link to the item that needs to be deleted.]
				[note][It does not matter where link for this menu item exists, it will find it automatically.]
			
			
				[note][This tag can be repeated to delete multiple menus.]
			
		
	
	To preview how upgrade works set  to 2802 or higher.
	
		[note][This tag can be repeated with all child tags for each release that needs upgrade action.]
		
			
				[string][This is a single actual sql query that will be executed on upgrade.]
				[note][SQL query be placed in  tags.]
				ALTER TABLE `_db_examplepluginanothertable` MODIFY COLUMN `user_id` int(30) unsigned NOT NULL;
			
		
		
			
				[contains][Nothing, works from parameters only.]
				[param][delete][string][mandatory][The link to the item that needs to be deleted.]
				[note][It does not matter where link for this menu item exists, it will find it automatically.]
			
			
		
	
	To preview how upgrade works set  to 2803 or higher.
	
		[note][Will run through all upgrade on upgrade until install version is met.]
		
			
				[note][The query tag should be repeated for each upgrade query.]
				DROP TABLE IF EXISTS _db_examplepluginanothertable;
			
		
		
			
		
		
			
				We can do this all day long (5)
				[note][Setting tag needs to be repeated for each setting added.]
			
		
	

The uninstall tag

The uninstall tag will mostly uninstall everything automatically, except for the queries. You need to explicitly tell the system what tables needs to be removed if any. Menus, classes, settings will all be permanently removed automatically.

	
		[contains][All actions that will be executed on uninstalling a plugin.]
		[note][Menus, settings and hooks will be uninstalled automatically for this plugin.]
		
			[contains][All uninstall queries that will be executed when requested.]
			
				[string][This is a single actual sql query that will be executed on uninstalling plugin.]
				[note][SQL uninstall query be placed in  tags.]
				DROP TABLE IF EXISTS _db_ExamplePlugin_example;
			
			
				[note][The query tag should be repeated for each uninstall query.]
				DROP TABLE IF EXISTS _db_examplepluginanothertable;
			
		
	
As you can see, no version information needs to be provided for the uninstall to take place. Just the latest SQL is needed.

Conclusion

That covers it for the configuration file for plugins. If you are still hesitant about features, just comment here and we will add continued help regarding your question. Happy coding.

Menus and Node Types

article_Screenshot - 07022012 - 15:05:08.png

You will always require an execution point (url) for your first controller or page. You can obviously turn any page you create into your application or sites landing page. However, here I will be explaining exactly what getting started gets to once you start developing more advanced controllers. When we refer to a script, controller or node, we generally refer to a single PHP file that you created.

Menus with its node types are a way to point to php files that will needs to run when a certain url is executed. PHPDevShell will then do all the security and load that controller in the shell of PHPDevShell. It is like running a php script in its own safe environment. Remember to also check out the Example Plugin for some good examples.

The power of menus

Not only can you create multiple instances of how a script should be executed from the menu system, you can also set exact permission on who can access that menu and also what can he see once he accessed it.

If you have not yet done so, please read getting started so you know how you go about creating a menu for your plugins. You will later learn how to create full installation sets of how you can make your plugin install with all its menus.

Menu node types

When assigning a controller/script to a menu, there are different node options you can select for them, each one means something different and will cause the controller/script to act differently.

Plugin Nodes are always developed the same way as you would develop a normal node (type 1), except they behave differently depending on the node type you choose.

Plugin Nodes

Plugin nodes has a node controller/script that links from the menu item and executes.

(1) Standard Web Page

This is a direct call and execution, this node controller will sit in your plugin folder and will execute and spit out whatever you wrote in PHP. This is generally the default type you will choose. This controller could be linked to something as simple as : plugins/MyPlugin/mysimplenode.php

	echo "Hello World, this is my first script in PHPDevShell.";

Screenshot - 07022012 - 14:18:46.png

(9) Widget Module

This little beauty allows you to call from any other node this node as ajax. So basically you develop this node just like the (1) Standard Web Page, except it is available to call as ajax with a very simple command. The way it will be called will be framed like you expect from a widget, like the example right:

(10) Ajax Call

Exactly like Widget Modules, except it loads the the node without the surrounding widget style border and heading.

(11) Lightbox

The lightbox node also loads from within a normal node, except it will pop up the lightbox type node as a neat lightbox page.

Link Existing Node

These node types links to existing nodes with the idea that it works like symlinks from the Linux file system. What this boils down to is the fact that you can create multiple menu items to a single controller. Each link could send specific information through and could perhaps change the behavior of a controller.

(2) Plain Link

This will create a link to an existing node, when clicked it will load the controller exactly as if it was its own controller.

(3) Jump To Link

Instead of linking to the controller/script directly, this when clicked will load the controller the original menu item links to and will also jump to the level where this menu sits, as if you clicked the original menu item.

(6) Place Holder

Serves no real purpose besides being a parent for a group of similar menu items. Nothing happens when you click on this menu item.

External File

(4) Load External File from outside plugin

In general this should not be used, however in some cases where you want to load a PHP file that has nothing to do with PHPDevShell, you could use this.

HTTP Link

(5) Normal external http link

Just a link to an external http address.

iFrame

(7) Http location inside iframe Height

In general this should not be used, it will create an iframe inside your theme and load the page of the url provided.

Cronjob Node

(8) Automatic cronjob

This very useful node type is created like any other plugin node. When selected as a cronjob node it will configure the node to appear in PHPDevShell cronjob manager. This allows you to manage scripts to execute in a timely fashion without human interaction. PHPDevShell can handle all the cronjob nodes differently and gives you allot of maintenance power.

Calling menus from inside nodes

Not always will you want to use the menu navigation system to access your script, you might want to create a link from your existing script, or you might want to call another node as ajax. Not only this you want it to be the same on all system, independent of friendly URL's being on or off.

When installing menus, an automatic unique menu id is created from the plugin path. This means one can determine what your menu id is going to be on all systems. This is useful for where you want to call a menu item from another controller. As long as the path to the file stays the same, the menu id will be the same on all systems that your plugin will get installed onto.

So lets use our first node type plugins/MyPlugin/mysimplenode.php as the example again. Lets say I want to create a link to the existing "Readme" screen of PHPDevShell, if I view the Menu Manager UI, I can clearly see that the ID for this menu item is 2266433229 which will always be the same on all systems as long as the controller path stays the same.

	echo "

Hello World, this is my first script in PHPDevShell.

"; // Lets create a link to another node. $url = $this->navigation->buildURL('2266433229'); // return example http:/ /somedomain.com/readme.html echo "

Click here to go to Readme.

";

That's not all...

Just touching the tip of the ice berg, this takes into account that you will be using the Menu Manager UI to create menu items. There are more sustainable ways of creating menu items and this is done by the plugin config and installer file. This topic is covered in later documentation page.

Making database queries

PHPDevShell supports from legacy PHP query functions to straight forward query methods moving on to more advanced models and ORM. You can obviously use whatever you want or write your own plugin that shares the way you like to do queries.

PHPDevShell will maintain your connection to the database, so you will be ready to make a query at any time. It does not matter if you just use php database functions or PHPDevShell methods or ORM, the query is ready to be made.

PHPDevShell main query system only supports MySQL at this stage. There are several reasons for this, one of the most prominent reasons are performance and complications that goes with a DB layer. You are still free to do queries to other database systems, however, the core will still run MySQL for now. We are following technology closely and will most definitely support multiple databases without performance interference.

So in this example we will create a single node, point a menu to use it and write the examples below. So lets create a file under plugins/MyPlugin/example-query.php, and create a menu item for it so we can access it.

Simple queries using PHP

Here we will focus on just using PHP to do a query, the way you are used to:

<?php
    // Hello world heading.
    echo "

Hello, world!

"; $query = mysql_query('SELECT * FROM pds_core_users WHERE user_id = 1'); $query = mysql_fetch_array($query); ?>

Simple queries using PHPDevShell

In general it is not a good idea to use PHP database function directly, so using the PHPDevShell layer we can do a simple query like this:

<?php
    // Hello world heading.
    echo "

Hello, world!

"; $query = $this->db->newQuery('SELECT * FROM pds_core_users WHERE user_id = 1'); ?>

Advanced queries with models

If you are looking at making serious applications you would want to use the PHPDevShell model structure that falls in with MVC. We will not cover the full topic here as it is quite advanced and needs a documentation page of its own. However in the end, a query will look like this;

<?php
    // Hello world heading.
    echo "

Hello, world!

"; $user_name = $this->db->invokeQuery('getUserName'); echo $user_name; ?>

For more information on advanced queries, have a look at MVC

Using ORM for queries

Then we have full support for ORM using the very well written light RedbeanORM. This system is brilliant for medium complexity websites. You will need to call the ORM plugin for this one, to do a query simple do;

<?php
    // Hello world heading.
    echo "

Hello, world!

"; // Call ORM plugin. $this->factory('orm'); // Do the query $books = R::find('example_book'); ?>

The advantage of using an ORM is that you will never need to look at the database, you handle data as code and the backend takes care of the rest. This also works well with forms and data imports. Database structure is created dynamically as you add data to columns etc. Look at the RedBean documentation to learn more about its features.

Conclusion

Lightly touching a very important subject the idea is to give you ideas on how you could do queries. Follow one or all methods, in later topics data is discussed in more detail.

Your first PHPDevShell script

article_jacob_computer-1024x768_0.jpg

Did you read the introduction yet? Now that you know that your first script will be in your own plugin, and that a menu connects the user to this script. Lets look at how we can start using PHPDevShell methods and function and merge with its shared environment. You will be happy to learn all the information and powerful methods you have at your fingertips. Once you learn how methods are divided into, you will be coding in no time!

How do you get shared resources?

PHPDevShell divides its classes and instances up in categories for your plugin to share. Not only does it keep information there it also keeps the most useful methods at your fingertips. It uses lazy loading so you don't have to worry about including incorrect files etc.

Because of the vast number of methods, you might want to consider using netbeans for auto-code completion. Currently as far as we know, only Netbeans is smart enough class structure for code completion.

The core classes

At first you will be using methods from the 5 most prominent classes. These classes gives you enough power to deliver most applications. They are all stored in ready to use instances and will be available to you instantly, wherever you are coding. So first thing is first, lets have a quick look at the most used class instances you will have available in your script;

$this->db
$this->navigation
$this->security
$this->template
$this->user
$this->tagger
$this->core 
// And an array with a whole lot of session information;
$this->configuration 
These are the root instances of all PHPDevShell methods and information you will use. However, not limited to these as there are many plugins that extends functionality of PHPDevShell.

Click on the headings to see all the methods per class as we will only cover single examples in each method.

$this->configuration

The configuration class will give you an array of information regarding the current logged in session. Lets look at some array information it stores;

<?php
	echo $this->configuration['user_display_name']; // Will show logged in users name.
	// Actually it contains allot of useful information;
	printr($this->configuration);
?>

$this->core

The core contains methods that does not really fit anywhere else, and focuses on simple methods but which needs to be handled in specific ways. For instance, lets say we want to show the current date and time in a settings formatted way;

<?php
// It contains many methods, this is just one example;
// This might sound simple, but PHPDevShell keeps track of the timezone.
$this->core->formatTimeDate(time());
?>

$this->tagger

The tagger class is an advanced class to group core elements, users, groups, roles and menus by tags and recalling them as a grouped unit when needed.

// It contains many methods, this is just one example;
$this->tagger->tag('user', $id, 'admin', 'Administrator');
$this->tagger->tagLookup('user', $id);
<?php
?>

$this->user

The user class contains methods to deal with users, it contains many useful administrator methods to deal with everyday

<?php
// It contains many methods, this is just one example;
?>

$this->template

The template method is used for displaying styled elements inside your script, it is generally styled from the main theme. It also contains very powerful methods falling inside this category.

<?php
// It contains many methods, this is just one example;
// Lets create a heading for a script.
$this->template->heading('My first script');
if (empty($this->configuration['user_email'])) {
	// This will display a neat warning message and log it with the database logger.
	$this->template->warning('Oops, you have no email');
}
?>

$this->security

The security class is mostly automated these days and you will use very few methods from it, but it does contain a few oldies that you might use.

<?php
// It contains many methods, this is just one example;
// Lets encrypt a string.
$foo = 'Hello World';
$encrypt = $this->security->encrypt($foo);
$foo = $this->security->decrypt($encrypt); // Contains 'Hello World'
?>

$this->navigation

The navigation methods contains very powerful methods to handle everything regarding your sites navigation, menus and seo. PHPDevShell works in the unique way of only knowing what the logged in user knows or is allowed to know. So if a user can only see Menu A, B, C the navigation model will only contain this for the user.

<?php
// It contains many methods, this is just one example;
// Lets see the current array of menu information and the data the active logged in user can view.
print_r($this->navigation->navigation);

// There are useful methods of always using the correct url independent of SEO settings.
echo "<a href={$this->navigation->buildURL('1231231231')}>Some Existing Url</a>";
?>

$this->db

The db is all about database and data access, it contains also all the important query methods. So when it comes to accessing the database, this is where you will look for tools. Note ORM does not work from here as it is an independent plugin.

<?php
// It contains many methods, this is just one example;
$query = $this->db->newQuery('SELECT * FROM database');
?>

Now that we have all these tools, lets build a proper script.

By this time, just to clarify where you should be standing when starting to code your first script;

  • Create a plugin folder under /plugins/MyFirstPlugin/
  • Create a file that you will be working inside the just created folder /plugins/MyFirstPlugin/first.php
  • Create a Plugin Type 1 menu linking to it from the Menu manager
  • Still unsure? Read this guide

Great you now have an empty php file and you can now start using the available PHPDevShell classes to create your first application. Even though your first application will run inside the default application type theme, your script will be able to run in any theme you create later on.

We will not be looking at MVC in this tutorial, we will just focus on one script for simplicity. MVC is a method where you split your HTML (view), logic (controller) and data (model) for ease of maintenance and practicality.

Going forward we will now call your script the controller. The correct way is to wrap your controller code inside the controller class of PHPDevShell. However, this is not even mandatory, you can still write your PHP code directly without extending the main PHPDevShell controller.

<?php
class myFirstApp extends PHPDS_controller
{
	// execute is the default controller method.
	public function execute()
	{
		// Your script code goes here...
	}
}
return 'myFirstApp';
?>

Ok, this was simple enough, now lets add a neat heading and some intro text to our application using some

$this->template

methods.

<?php
class myFirstApp extends PHPDS_controller
{
	// execute is the default controller method.
	public function execute()
	{
		$this->template->heading('My First App');
		$this->template->info('This is just my first sandbox application in PHPDevShell, I like what I see so far!');
	}
}
return 'myFirstApp';
?>

By now your application should look something like this:

Screenshot - 09022012 - 09:57:48.png

Normally I wont add html in my controller, this really belongs in the view, but for this example we will do it for simplicities sake. For now we will just kinda keep the html and controller seperated in two different methods. Lets create the form in the same controller class and call the method myFirstForm;

<?php
class myFirstApp extends PHPDS_controller
{
	// execute is the default controller method.
	public function execute()
	{
		$this->template->heading('My First App');
		$this->template->info('This is just my first sandbox application in PHPDevShell, I like what I see so far!');
		$this->myFirstForm();
	}

	private function myFirstForm()
	{
		// Lets create some form.
		$FORM_HTML = <<
				

HTML; echo $FORM_HTML; } } return 'myFirstApp'; ?>

Once we are done with this, lets create a method to handle the form when the submit button is pushed.

<?php
class myFirstApp extends PHPDS_controller
{
	// execute is the default controller method.
	public function execute()
	{
		$this->template->heading('My First App');
		$this->template->info('This is just my first sandbox application in PHPDevShell, I like what I see so far!');
		$this->handleForm();
		$this->myFirstForm();
	}

	private function myFirstForm()
	{
		// Lets create some form.
		$FORM_HTML = <<
				

HTML; echo $FORM_HTML; } private function handleForm() { if (! empty($_POST)) { if (! empty($_POST['name'])) { $this->template->ok(sprintf("Thanks girlfriend, the name {$_POST['name']} was submitted.")); } else { $this->template->warning('Are you sick in your head, no name was given!?'); } } } } return 'myFirstApp'; ?>

Mmm... this is really it, now if you hit the submit button the handleForm method will detect it and handle the data. Now you have a clean, protected first application in PHPDevShell. So your end results should be something like this too:

Screenshot - 09022012 - 11:03:56.png

Remember to check out the ExamplePlugin that ships with PHPDevShell to view this example and many more useful examples.

MVC

PHPDevShell provides a great standard MVC structure for you to keep your code tidy and organized. You can use your own preferred MVC style or ours, which is neat. Keeping to the formula will make others comfortable with your code.

The MVC structure

It should be noted that PHPDevShell shares its resources and objects with everything in its structure, these are generally divided into object groups and is available as;

There are 7 main objects you can use in PHPDevShell to assist you with development, they are;

$this->db
$this->navigation
$this->security
$this->template
$this->user
$this->tagger
$this->core 

you also have a range of PU_utility functions. Most of the methods in PHPDevShell tells a story like,

$this->user->isRoot(), $this->user->belongToGroup() or $this->navigation->createMenuId()

Hopefully our easy naming convention will make you remember them easy. Have a look at the API docs to see an overview of classes and their methods, look specifically for the main 7 objects.

Inside a plugin you have multiple folders to assist you in your structure, these are;

config

The config folder contains the plugins xml configuration file for installing and un-installing a plugin.

controllers

This is the container for the controllers a menu item will call to execute your script. The controller is meant as an execution point using basic logics to bring queries/models and views together. Using the Menu UI Admin, you can assign a controller item to a menu and it will be available from the navigation system to execute.

a Controller is named anything, it could be as simple as;

controllers/some-item.php

Inside the controller you would start with this;

/**
 * CONTROLLER: Simple readme to introduce MVC in PHPDevShells plugins.
 *
 * @author Jason Schoeman
 * @return string
 */
class ReadMeExample extends PHPDS_controller
{

	/**
	 * We always start by overriding the execute method for the controller.
	 * @author Jason Schoeman
	 */
	public function execute()
	{
		// a Default heading.
		$this->template->heading(_('Oooh an Example Plugin'));
		// Some info regarding this node.
		$this->template->info(_('The best way to learn is to learn by example.'));

		// All your code...

		// invokeQuery is the model of PHPDevShell.
		// Each query is in its own class.
		// There can be a model for each controller. So the model will be under ExamplePlugin/models/readme-example.query.php
		$some_data_call = $this->db->invokeQuery('ExamplePlugin_someData');

		// This is how you load a plugin, in this case we have registered 'views' plugin class and calling it.
		// This plugin is using Smarty to split HTML from code.
		$view = $this->factory('views');

		// We now set some variables to the VIEW of PHPDevShell, this splits the Code and HTML.
		$view->set('some_variable', $some_data_call);
		$view->set('developer_name', 'Linus Patrovidge');

		// Output View.
		// The view also has the same name as the controller, you can find the view in ExamplePlugin/views/readme-example.tpl
		$view->show();
	}
}

models

The model is data objects required. For instance if I need the names of all my Grade 3 students, I will call a model via a query to request this data, the model will return this data to me. Models does not have to be a database query, it could be any type of data from any source. In general models are called with;

// invokeQuery can be used anywhere really, controller, classes, models.
$this->db->invokeQuery('ExamplePlugin_editExampleQuery', 10);
// The 10 just means I am calling ID 10.

The model will be lazy loaded once required. The model with all its objects is named in relation to its controller called "some-item.php" could have a model in

models/some-item.query.php

and generally have model objects like these;

class ExamplePlugin_editExampleQuery extends PHPDS_query
{
	protected $sql = "
		SELECT
			id, example_name, example_note, alias
		FROM
			_db_ExamplePlugin_example
		WHERE
			id = %u
    ";
	protected $singleRow = true;
}

Read more about PHPDS models here...

views

Views is the final layout to the browser, PHPDevShell attempts to keep this in its purest HTML using a plugin called Smarty, again you can create your own views plugin but this is a very clean way of doing. If you dont mind mixing HTML and PHP you can simply call your own PHP view file from your controller, this is really up to you though.

Views are also lazy loaded in relation to the controllers name, a typical view will look like this

views/some-item.tpl

, it will be called from the controller like in the controller example above and look something like this;

Hi {$developer_name}, welcome to the Example Plugin

includes

This can contain helper classes to help your controller or models to achieve repetive tasks, a typical class will look something like this and can also be lazy loaded. The name of the class like "myClass" should be the same as the file name in the includes folder, like myClass.class.php.

a Class can also be registered in the plugins configuration file. This have great advantages, any plugin can share this class with great ease, and a class can be overriden with a class of the same name and a higher ranking in the registry.

A Typical class will start like this.

class myClass extends PHPDS_dependant
{
	public function example () 
	{
		// All resources is available.
		$this->template->ok('Some ok message');
	}	
}

This can then be called from anywhere using;

$myClass = $this->factory('myClass');
$myClass2 = $this->singleton('myClass'); // Shared instance of $myClass.
$myClass->example();

resources

Resources folder is for plugins that calls third party classes or api's. As an example, PHPDevShell uses TinyMCE wysiwyg editor. Obviously PHPDS uses its own classes to initiate everything, but the actual TinyMCE javascript is being called from the resource folder. This means that the TinyMCE download was copied into the resources folder.

images

Simply holds all your images, logos and control panel icons related to your plugin.

language

Language holds a translations and dummy language extractions for your plugins.

Models

Models are the basis for every single application, since every application has data. PHPDevShell attempts to make data management in the MVC structure as easy as possible. We see all data as objects, and these objects can be any form of data. If it contains information, it can be queried and the data will be returned. Queries can include actual database queries, functions, or manually set data.

Note that when referring to a folder or file, it means it is a folder or file inside your own plugin folder.

Invoke a data Query

It does not matter if you are in a helper class, controller or even a model itself, you can always execute a query request using:

$this->db->invokeQuery('nameOfDataClass');

The query you are calling sits in a file that has the same name as your controller. For instance, if you have a controller called

my-controller.php

the model with all the queries will be called

my-controller.query.php

in the models folder.

A model file can also be reused in other controllers/models/classes by simply including the filename from wherever else it is needed.

Each data query is a class of its own, meaning for each data query you will create a class like this:

class nameOfDataClass extends PHPDS_query
{
	// Class always extends PHPDS_query
	/**
	 * Override invoke
	 * @return string
	 */
	public function invoke()
	{
		$array = array('Data Value 1', 'Data Value 2');
		return $array;
	}
}

Now whenever we call

$array = $this->db->invokeQuery('nameOfDataClass');

we will have the data that 'nameOfDataClass' returns

The use of the optional public function invoke() from within the query class always instructs the class that you wish to return custom data. You can either manipulate data from a database query or return data that has nothing to do with the database at all.

The Database Layer

The query system does not stop there. It allows you to query the database with powerful and easy to use database functions with really little effort.

So lets do our first read query. Lets just get the results from the database. One query file can have multiple class objects, in our example file my-controller.query.php we write

class exampleQuery extends PHPDS_query
{
	protected $sql = "
		SELECT
			id, example_name, example_note
		FROM
			_db_ExamplePlugin_example
    	";
}

Now if we call this data from our controller with something like this:

$array = $this->db->invokeQuery('exampleQuery');

This will return return an array ready to be used or looped.

NOTE: Read $keyField Data Query Switch below to see how results are returned from the query.

Passing data to query

Obviously life is not that simple and we will usually need to modify the sql conditions in our query. How would we handle this?

The query model handles whatever you throw at it. Passing as many variables to it is very easy actually.

Lets look at the example;

class exampleQuery extends PHPDS_query
{
	protected $sql = "
		SELECT
			id, example_name, example_note
		FROM
			_db_ExamplePlugin_example
		WHERE
			example_name = '%s'
		AND
			example_note = '%s'
    	";
}

As can be seen, in this example, we need to pass through two values to the database query for the WHERE clause. To do this is simple;

$array = $this->db->invokeQuery('exampleQuery', 'Jason', 'Hello');

Now 'Jason' and 'Hello' will be included in the WHERE condition and only the required results will be returned. The variables are used in the order passed. So Jason is the first variable passed so it will be used with the first occurrence of a data type specifier, in this case the %s in example_name.

NOTE: The %s stands for string and is a data type specifier that specifies the type of data being passed into the query (%s for string, %u for unsigned integer, etc). These are the same as what PHP's sprintf function would use. See the sprintf documentation for more details at http://php.net/manual/en/function.sprintf.php.

This use case is simple but has two problems: you have to know the order of the parameters, and if a parameter is used twice, you have to pass it twice. To avoid these, just use named parameters:

class exampleQuery extends PHPDS_query
{
	protected $sql = "
		SELECT
			id, example_name, example_note
		FROM
			_db_ExamplePlugin_example
		WHERE
			example_name = '%(name)s'
		AND
			example_note = '%(note)s'
    	";
}

To use this query, you have to use an associative array:

$array = $this->db->invokeQueryWith('exampleQuery', 
		array('name' => 'Jason', 'note' => 'Hello')
	);
NOTE: you can ask the DB to invoke a query in two ways, either by adding the parameters directly inside the function call, or WITH an array. Note the difference:
$array = $this->db->invokeQuery('exampleQuery', 'Jason', 'Hello')
	);
$array = $this->db->invokeQueryWith('exampleQuery', 
		array('name' => 'Jason', 'note' => 'Hello')
	);

Data query switches

The query object allows you to request data manipulation through simple switches. There are a few switches available. Here they are with a short explanation:

	/**
	 * The name of the field to use as a key.
	 *
	 * By default this is set to the special value '__auto__', which means the class
	 *  will automatically figure out which field/column to be use as the key
	 * If you want the returned array keys to be set to a specific field/column
	 *  value, then you just need to specify that field/column name in $keyField
	 * If you want the returned array keys to be set to a numerical index
	 *  starting at 0, then you need to set $keyField=false
	 * @var string
	 */
	protected $keyField = '__auto__';

	/**
	 * Make a field the point of interest
	 *
	 * This field changes the way some arrays are returned:
	 * - if $focus contains a field name, a row will be the value of this field (scalar) instead of an array of all values in the row
	 * - if the row doesn't contain a field an empty value is used for the row
	 * @var string
	 */
	protected $focus = ''; // can be empty for 'no' or any other value for field name

	/**
	 * strips any row with no content
	 * @var boolean
	 */
	protected $noEmptyRow = false;

	/**
	 * Guidelines to typecast/forcecast the result data
	 *
	 * @var string | array of strings
	 */
	protected $typecast;

	/**
	 * The first line of the result is returned instead of a one-line array
	 *
	 * @var boolean
	 */
	protected $singleRow = false;

	/**
	 * Automatically escape bad chars for all in-parameters
	 * @var boolean
	 */
	protected $autoProtect = false;

	/**
	 * Instead of the query result, returns the last_insert_id()
	 * @var boolean
	 */
	protected $returnId = false;

	/**
	 * Return one value from the asked field of the asked line
	 * @var boolean
	 */
	protected $singleValue = false;

To request data manipulation in a query, simply include the switch in the database object class, for example;

class editExampleQuery extends PHPDS_query
{
	protected $sql = "
		SELECT
			example_name
		FROM
			_db_ExamplePlugin_example
		WHERE
			id = %u
    	";
	protected $singleValue = true;
}

We are basically telling the query system to call the query, but don't return an array, only return the required value. Now only column name will be returned directly.

To further the example, say for instance we want to return the ID of the row we just inserted we can also add the following switch;

class writeExampleQuery extends PHPDS_query
{
	protected $sql = "
		INSERT INTO
			_db_ExamplePlugin_example (id, example_name, example_note, alias)
		VALUES
			(%u, '%s', '%s', '%s')
    	";
	protected $returnId = true;
}

Note the %u and %s are data type specifiers (string=%s and unsigned integer=%u). These are the same as what sprintf would use.

Lets invoke the query and get the returned id;

$id = $this->db->invokeQuery('exampleQuery', '', 'Jason', 'Some Note', 'hello');
// $id now contains the returned database id.

Checking or altering parameters before executing the query

Not all parameters of a query are to be specified when invoking it. For example, a query could use the current user ID. Let's look at this example which extends the query above:

class editMeExampleQuery extends editExampleQuery
{
	
	public function checkParameters(&$parameters = null)
	{
		$me = $this->user->currentUserID();
		$parameters = array($me);

		return true;
	}
}

The method checkParameters() is called just before the query is actually sent to the database. It allows the query object to check the caller's parameter values and/or inject its own values. It can also prevent the query from being sent (if for example some parameters are missing).

As you can also see in this example, queries are real OOP entities, and therefore enjoy all the mechanisms like extending an existing class.

Checking or altering results after executing the query

The data spit from the database sometimes need a little polishing before being sent back to the caller. A query object has the opportunity to ensure its results are conformant to what the caller expects. See this example:

class editPrettyExampleQuery extends editExampleQuery
{
	
	public function checkResults(&$results = null)
	{
		return (is_string($results)) ? "User '$result'" : false;
	}
}

The method checkResults() allows the query to check and/or alter the result data before sending it back. In this case, it adds the word "User" before the user name if the query returned one, or signals an error (by returning False) if not.

Further enhancing database objects before they are returned.

Like everything else in PHPDevShell, the query can be overridden. In many cases you would want to manipulate data, further enhancing queries and filtering your data before it is returned to whatever requested it.

To do so is straight forward, you will override the invoke (using public function invoke()), do the query and return the data. To override invoke we will do this;

class exampleQuery extends PHPDS_query
{
	protected $sql = "
		SELECT
			id, example_name, example_note
		FROM
			_db_ExamplePlugin_example
		WHERE
			example_name = '%s'
		AND
			example_note = '%s'
    	";

	/**
	 * Override invoke
	 * @return string
	 */
	//NOTE: Adding this "public function invoke" is how we override and
	//      manipulate the data returned from the SQL query.
	public function invoke($parameters)
	{
		// We are now in a new override instance of invoke.
		// First lets capture the data that is being passed to parameters.
		list($example_name, $example_note) = $parameters;
		
		// We can manipulate the data now, lets strip white spaces from $example_note as an example.
		$example_note = trim($example_note);

		// We can now do the query as normal and further manipulate data. To do this we do the original invoke.
		$array = parent::invoke(array($example_name, $example_note));

		// Now we can loop the data and do whatever we want with it.
		foreach ($array as $values) {
			$id = $values['id'];
			$name = $values['example_name'];
			$count ++;
			$new_array[] = array($id, $name, $count);
		}
	
		// We can even do more queries from here.
		$this->db->invokeQuery('MoreQueries');

		return $new_array;
	}
}

As you can see, you truly have full control. Remember that you can invokeQueries inside other queries, or include any .query.php file in any other file, allowing you to reuse queries over and over again.

Passing an array of values instead of individual values

What if I have an array of values that I want to pass with invokeQuery, instead of passing individual values? Say for example:

$where_clause = array('Jason', 'Hello');
$array = $this->db->invokeQuery('exampleQuery', $where_clause);

Since the values passed are placed into an array, we would need to override the invoke to pull them back out. Notice in the code below that there is only one change.. See remarks:

class exampleQuery extends PHPDS_query
{
	protected $sql = "
		SELECT
			id, example_name, example_note
		FROM
			_db_ExamplePlugin_example
		WHERE
			example_name = '%s'
		AND
			example_note = '%s'
    	";

	/**
	 * Override invoke
	 * @return string
	 */
	//NOTE: Adding this "public function invoke" is how we override and
	//      manipulate the data returned from the SQL query.
	public function invoke($parameters)
	{
	/* Since we passed in an array of values, that array will be indexed into an array at index[0], so we need to get them back out.
	 * NOTICE: The only change to the below code is that we changed this:
	 * = parameters;
	 * to this:
	 * = $parameters['0'];
	 */
		// We are now in a new override instance of invoke.
		// First lets capture the data that is being passed to parameters.
		list($example_name, $example_note) = $parameters['0'];
		
		// We can manipulate the data now, lets strip white spaces from $example_note as an example.
		$example_note = trim($example_note);

		// We can now do the query as normal and further manipulate data. To do this we do the original invoke.
		$array = parent::invoke(array($example_name, $example_note));

		// Now we can loop the data and do whatever we want with it.
		foreach ($array as $values) {
			$id = $values['id'];
			$name = $values['example_name'];
			$count ++;
			$new_array[] = array($id, $name, $count);
		}
	
		// We can even do more queries from here.
		$this->db->invokeQuery('MoreQueries');

		return $new_array;
	}
}

Register a class

Sometimes models becomes complex and the same model is used over and over again. When this is the case it is highly recommended to register a class instead, add the class in includes and then call it with the factory method.

This is simpler than it might sound. First we will create the class inside your plugins includes folder. The class must be named with .class.php at the end to enable on-demand loading.

So lets call this class;

includes/myClass.class.php

Obviously a helper class should have its own models file to do its queries in, this is done exactly like with normal controllers, in the root models folder we add;

models/myClass.query.php

You can now invokeQueries from your helper class called myClass.class.php like you would expect.

The helper class is generally called from the controller or model file, but there is no right location for calling it, just wherever it is needed. A helper class must start with the name you called the file, it should also extend PHPDS_dependant, this allows it to share resources;

class myClass extends PHPDS_dependant
{
	public function someMethod ()
	{
		// We can also invoke queries.
		$name = $this->db->invokeQuery('getName');
		return "Hello $name";
	}
}

To call helper classes is easy too.
NOTE: You might want to register your class in your plugins config file enabling you to share your class with all your plugins.

Lets call our helper;

$myClass = $this->factory('myClass');
echo $myClass->someMethod();

TRANSACTIONS AND ROW LOCKING

There will be certain situations where you need to use START TRANSACTION and COMMIT to perform a database query and lock a row so no one else can access it until you are done with it. If you are using MySQL, the InnoDB engine supports this type of transaction and row locking.

NOTE: Many servers using MySQL have the default engine set to MyISAM. The MyISAM engine can NOT perform transactions or row locking. You need to use an engine like InnoDB which supports transactions and row locking.
For more information on the difference between MyISAM and InnoDB engines, here is a good comparison:
http://tag1consulting.com/MySQL_Engines_MyISAM_vs_InnoDB

Let's look at an example where we need to retrieve a unique number, and need to make sure nobody else retrieves that same number. Here are the steps we need to perform:

    #1- Use START TRANSACTION.
    #2- Use SELECT with a FOR UPDATE to lock the row and retrieve the number.
    #3- UPDATE the row with a new number for the next person.
    #4- Use COMMIT to unlock the row for others.

DATABASE TABLE example

Here is the SQL for the test database table we will use in this example:

CREATE TABLE `pds_test_unique_number` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `prefix` text COLLATE utf8_unicode_ci NOT NULL,
  `unique_number` int(11) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

INSERT INTO `pds_test_unique_number` (`id`, `prefix`, `unique_number`) VALUES
(1, 'ABC', 1000001);

CONTROLLER file example

Our controller file will look like this:

<?php
class getUniqueNumber extends PHPDS_controller
{
	public function execute()
	{
		//Give it a nice heading
		$this->template->heading(_('GET UNIQUE NUMBER'));

		//Call our query that will return a unique id
		$unique_number = $this->db->invokeQuery('myPlugin_getNewUniqueId');

		//Call the template file
		$view = $this->factory('views');
		$view->set('unique_number', $unique_number);
		$view->show();
	}
}
return 'getUniqueNumber';

MODEL file example

Our model file will look like this:

<?php
class myPlugin_getNewUniqueId extends PHPDS_query
{
	public function invoke($params)
	{
        
        //Need to START TRANSACTION so we can lock row.
	//NOTE: To lock a row with SELECT you need to use FOR UPDATE.
        $this->db->startTransaction();

        //Get the next unique id
        $results = $this->db->invokeQuery('myPlugin_getNextAvailableUniqueId');

	//Extract the row id and the unique_id from the returned query
        $id = $results['id'];
        $unique_id = $results['unique_id']; 
        
        //Increase the number portion of the id by one in the table for the next person
        $this->db->invokeQuery('myPlugin_increaseUniqueIdByOne', $id);

        //Need to COMMIT so we can unlock row
        $this->db->endTransaction();
        
        //return $id;
        return $unique_id;
	}
}

class myPlugin_getNextAvailableUniqueId extends PHPDS_query
{
    protected $sql = "
        SELECT id,prefix,unique_number 
        FROM pds_test_unique_number 
        LIMIT 1 
        FOR UPDATE;
    ";
    protected $keyField=false;

	public function invoke($params)
	{
        //Invoke the db query.
	//NOTE: The SELECT query needs to use FOR UPDATE in order to lock the row.
	$results = parent::invoke();
        
        //Lets get the db row id that was used so we can update it later
        $id = $results[0]['id'];
        
        //UNIQUE ID is a combination of the following two fields, so let's put together.
        $unique_id = $results[0]['prefix'].$results[0]['unique_number'];
 
        //Return the results
	return array('id' => $id, 'unique_id' => $unique_id);;
	}    
}

class myPlugin_increaseUniqueIdByOne extends PHPDS_query
{
    //This will increment the specified field by one for the next person.
    protected $sql = "
        UPDATE pds_test_unique_number 
        SET unique_number = unique_number + 1
        WHERE id=%u
        LIMIT 1;
    ";
}
Remember, models can be reused by simply including one controllers model in another controller using standard php include;
require_once 'plugins/ExamplePlugin/models/someModel1.query.php';
require_once 'plugins/ExamplePlugin/models/someModel2.query.php';
// Now I can use their invokes.
$this->db->invokeQuery('queryFromModel1');
$this->db->invokeQuery('queryFromModel2');

VIEW file example

Our view file will look like this:

The unique id is: {$unique_number}






This documentation will be extended soon, there are many more advanced uses available for queries and the models system...

Model snippets

When writing plugins, you will often build on repetitive little skeletons of code. You'll find here several empty snippets for various tasks; just copy and paste them, then start from there to write your own stuff.

PHPDS_query

class MY_query extends PHPDS_query
{
	protected $sql = 'SELECT db_field_1, db_field_2 FROM _db_tablename WHERE db_field_1 > 1';
	public $single_value = true;
}
class MY_query extends PHPDS_query
{
	protected $fields = array(
		'db_field_1' => 'Label of first column',
		'db_filed_2' => 'Label of second column'
	);
	protected $tables = '_db_tablename';
		// prefix '_db_' will be changed for the installation-specific prefix
	protected $where = 'db_field_1 > 1';
	protected $groupby = 'db_field_2';
	protected $orderby = 'db_field__2';
	protected $limit = '0,1';
	public $single_value = true;
}

Views

The view is the HTML part of your page, what the user sees. Each controller has its own view. PHPDevShell offers the brilliant template engine called Smarty 3 for its views presentation. This offers you the ability to really keep your html and php separate. PHPDevShell also offers a standard PHP view too if you prefer not to use Smarty.

Most of the logic happens in the model and controller files. You can also add logic in your view file too, but the problem is that mixing together PHP and HTML looks very messy and is hard to read when it gets complicated. This is why we recommend the use of Smarty, and all the logic stays in your model and controller files. However, PHP is a scripting and template language by itself, so you can choose to just stick with PHP in your view files and not use Smarty at all!

Obviously you can integrate your own template engine if need be, just create a plugin for it and it will be accessible to all your plugins. The two types we chose as standard plugins are clean PHP and Smarty.

Smarty Views

The Smarty design was largely driven by these goals:

  • clean separation of presentation from application code
  • PHP backend, Smarty template frontend
  • compliment PHP, not replace it
  • fast development/deployment for programmers and designers
  • quick and easy to maintain
  • syntax easy to understand, no PHP knowledge required
  • flexibility for custom development
  • security: insulation from PHP
  • free, open source

Smarty is a template engine for PHP, facilitating the separation of presentation (HTML/CSS) from application logic. This implies that PHP code is application logic, and is separated from the presentation.

Integrating Smarty in your controller

Smarties .tpl files (also called your template file or View file) works exactly like the Smarty documentation explains, syntax is exactly the same. The only difference comes in when calling and setting Smarty in your controller. Lets look at a typical controller and calling Smarty from it.

Lets say we have a controller called

controllers/my-controller.php
class myController extends PHPDS_controller
{

	/**
	 * Execute Controller
	 * @author Jason Schoeman
	 */
	public function execute()
	{
		// Header information
		$this->template->heading(_('Simple example to list items in a database.'));
		$this->template->info(_('You can always use the info heading to provide some info regarding current item.'));

		// PHP code and logics, assume we have collected a bunch of variables here.

		$array = array('123', 'ABC', '456XYZ');

		// From here we load the views plugin which by default is Smarty. 
		$view = $this->factory('views');

		// Use set to assign arrays and variables to be send to the views.
		$view->set('my_name', 'William');
		$view->set('surname', 'Wallis');
		$view->set('some_array', $array);

		// When we call show, it will load the correct view under;
		// views/my-controller.tpl
		// Note it directly related naming then your controller file.
		$view->show();
		// You can also specify custom view like.
		// $view->show('my-controller2.tpl');
	}
}

return 'myController';

After you have done the assignments, its time to head over to your tpl file, ready to be flooded with clean html. The default tpl file location for the controller example as above would be under (create file manually)

views/my-controller.tpl

My name is {$my_name} {$surname}

See its really easier then you think.

Simple loop:

{foreach $some_array as $name} {strip} {$name}
{/strip} {/foreach}

Smarty offers very powerful tools to enhance your views, their documentation is extensive and complete, hop over here to see more regarding Smarty documentation.

PHP Views

When it comes to templating in PHP, there are basically two camps of thought. The first camp exclaims that "PHP is a template engine". This approach simply mixes PHP code with HTML. Although this approach is fastest from a pure script-execution point of view, many would argue that the PHP syntax is messy and complicated when mixed with tagged markup such as HTML.

The second camp exclaims that presentation should be void of all programming code, and instead use simple tags to indicate where application content is revealed. This approach is common with other template engines (even in other programming languages), and is also the approach that Smarty takes. The idea is to keep the templates focused squarely on presentation, void of application code, and with as little overhead as possible.

We provide availability to both methods and the PHP views is just as easy. Your controller called

controllers/my-controller.php

will now look like this;

class myController extends PHPDS_controller
{

	/**
	 * Execute Controller
	 * @author Jason Schoeman
	 */
	public function execute()
	{
		// Header information
		$this->template->heading(_('Simple example to list items in a database.'));
		$this->template->info(_('You can always use the info heading to provide some info regarding current item.'));

		// PHP code and logics, assume we have collected a bunch of variables here.

		$array = array('123', 'ABC', '456XYZ');

		// Use set to assign arrays and variables to be send to the views.
		$my_name = 'William';
		$surname = 'Wallis';
		$some_array = $array;

		// To make resources available in the scope of your view, we call the php view with the PHP function include like this;
		// The file that will be loaded by default is called views/my-controller.tpl.php
		// Note it directly related naming then your controller file.
		include $this->template->getTpl();
		// You can also specify custom view like.
		// $view->show('my-controller2.tpl');
		// Will load views/my-controller.tpl.php
	}
}

return 'myController';

The view will now look like this while sharing all resources,

My name is <?php echo $my_name ?> <?php echo $surname ?>

See its really easier then you think.

Simple loop:

<?php foreach ($some_array as $name) { echo $name . "
"; } ?>

In the end, it does not matter which direction you choose to go, its what makes you the most productive that counts. It should be noted that there are some nice caching features available for Smarty integrated into PHPDevShell. Smarty uses very little extra load compared to using PHP views.

Advanced Views

article_Swimming_Pool_View_1.jpgSince PHPDevShell V3.1.4+ only;

PHPDevShell has a unique and simple way of splitting your MVC. However, we found that in some instances this is not enough to keep the view neat and clean. It was very possible that you ended up pushing javascript from within your controller. With a large and complicated project this will quickly become messy.

Another scenario is where you don't want to use Smarty or any kind of template engine. Although PHPDevShell does allow php view files, this too is not OOP and gets very messy quickly. This made us introduce a new way of handling views, views through classes. The idea is to move anything that is a mix of PHP, shared instances and javascript there. Those that do not enjoy being in the tpl either.

Like models and views join the controller, so does the views/someName.view.php file. The file location would be the same as your tpl, except this will be a class file.

Lets look at an example of where the view class is being looked for; If he had a controller, named readme.php;

Screenshot at 2012-03-08 15:21:52.png

Nice, now we know where a view class will be looked for, as it is clear that it is related to the controllers name. a view class is created exactly like a controller class, very simple.

<?php

class readmeView extends PHPDS_view
{
	public function execute()
	{
		echo '

Hello World!

'; } } return 'readmeView';

This will now execute once the controller runs, you still have access to all the shared PHPDevShell properties and methods, now you can go wild with views.

PHPDevShell used to add allot of jquery changes by default, this was not a good practice as it became heavy and many pages did not need many of the elements. To rectify this we split these jquery functions up and it can be easily called to make your page look the same as the rest of PHPDevShell. Here they are...

  • $template->styleForms();
  • $template->styleButtons();
  • $template->styleFloatHeaders();
  • $template->styleTables();
  • $template->stylePagination();

For instance, say we have some javascript we want in the head tag of the template, we can silently push this through. Also note that I am calling some other styling elements which jquery does for us, it is a shortcut to modules.

<?php

class readmeView extends PHPDS_view
{
	public function execute()
	{
		$template = $this->template;

		$template->styleFloatHeaders();
		$template->styleTables();
		$template->styleButtons();

		$this->template->addJsFileToHead('themes/cloud/js/quickfilter/jquery.quickfilter.js');
	}
}

return 'readmeView';

Using the class as a view with HTML in it...

The power comes with the ability that you can pass values, arrays and objects from the controller to the view class. We will now create a simple view page using only the PHPDS_view class, no tpl file will be needed for instance;

Pass values from the controller like this;

<?php

class readmeExample extends PHPDS_controller
{
	public function execute()
	{
		$this->set('foo', 'Foo Bar');
		$this->set('other', array("Hello", "World"));
	}
}

return 'readmeExample';

Now we can catch this from the view class again, and have a view that looks something like this as an end result;

<?php

class readmeView extends PHPDS_view
{
	public function execute()
	{
		$template = $this->template;

		$template->styleFloatHeaders();
		$template->styleTables();
		$template->styleButtons();

		$this->template->addJsFileToHead('themes/cloud/js/quickfilter/jquery.quickfilter.js');

		$var = $this->helloWorld();
		echo "

{$var[0]} {$var[1]}

"; echo "

{$this->get('foo')}

"; // We can also go mad and send js or css to the head to change the overall design of the master theme. // Get ajax search ajax menu. $livesearch = $this->navigation->buildURL(null, "callback="); // For live ajax search we need to throw some javascript to the template head. $JS = <<template->addJsToHead($JS); // This is not where it stops, lets pass on some css. $this->template->addCSSToHead('.class { font-weight: bold; }'); } public function helloWorld() { return $this->get('other'); } } return 'readmeView';

We hope this tutorial gives you a good idea to the power achievable through the view class mechanism.

Controller

The controller is what the name suggests. Its a clean and simple file that glues the models and views together with basic controller logic. a Controller should never have complicated logic or data function inside it, although it will still work.

a Controller is the first file that is called by the navigation system when it is selected. The controller then decides what actions to take, data to load and then pushes this load to what the end user views.

Creating the controller

The controller can stand alone, this is the node the menu will access when executed. The first thing to do is create the controller file. Inside your plugins folder create a sampled name file called;

controllers/example-control.php

Inside your example controller add the following content;

class exampleControl extends PHPDS_controller
{
	/**
	 * Execute Controller
	 * @author Jason Schoeman
	 */
	public function execute()
	{
		$this->template->heading(_('Example Controller.'));
		$this->template->info(_('This is an example controller.'));
		// Continue coding...
	}
}

return 'exampleControl'

Once you have your controller ready, you can link a menu item to it;

Declaring a menu make an item appears in the dashboard which when clicked will run our script. Go to System Management -> Menu Admin -> New Menu -> Fill the fields as as:

Plugin Item: Standard Plugin (leave rest of left column blank).

Name: The display name visible to the user ; let's try "Example Controller" (note that this name can be different based on the user's language)

Alias: an alternate name, which can be used from the code to refer to the menu whatever the user's language ; let's try "hello-world"

Plugin Name/Folder: the name of the folder your plugin reside in; we chosed "tutorial" earlier

URL or path: this will tell the engine where exactly to find our script file ; it's "example-control.php"

Leave the rest to the default values and click "Save". Then click on "Dashboard" in the upper menu: your new menu item should appear, when executing it, it should show your content.

Controller content

Now that your controller is executable, you can fill it with code, including bringing models and views.

a Typical controller will eventually look something like this;

class manageExample extends PHPDS_controller
{

	/**
	 * Execute Controller
	 * @author Jason Schoeman
	 */
	public function execute()
	{
		// Define.
		$id = 0;
		$example_name = '';
		$example_note = '';
		$alias = '';

		// Lets create header information.
		if (!empty($this->security->get['ee']) || !empty($this->security->post['save'])) {
			$this->template->heading(_('We are in edit mode, lets edit an example.'));
			$this->template->info(_('By using PHPDevShells symlink to another items feature. It is possible to symlink to the orginal manage-example to handle edits.'));

			// Set to edit mode.
			$edit_mode = true;

			// Set edit example id.
			(!empty($this->security->get['ee'])) ? $ee = $this->security->get['ee'] : $ee = 0;
	
		} else {
			$this->template->heading(_('Add new Example'));
			$this->template->info(_('On this page we will show you how to do a simple form and save data to the database.'));

			// Set to edit mode.
			$edit_mode = false;
			
			// Set edit group id.
			$ee = 0;
		}
		// If item gets posted, lets read it and save it to the database.
		if (!empty($this->security->post['save'])) {
			if (!empty($this->security->post['id'])) {
				$id = $this->security->post['id'];
			} else {
				$id = 0;
			}
			if (! empty($this->security->post['id']))
				$id = $this->security->post['id'];
			$example_name = $this->security->post['example_name'];
			$example_note = $this->security->post['example_note'];
			$alias = $this->security->post['alias'];

			// Some minor error checking.
			if (empty($example_name)) {
				$this->template->warning(_('You need to provide values for all fields, please try again.'));
				$error[1] = true;
			}
			// If everything is ok we can now save.
			if (empty($error)) {
				$id = $this->db->invokeQuery('ExamplePlugin_writeExampleQuery', $id, $example_name, $example_note, $alias);
				// Show item updated.
				$this->template->ok(sprintf(_('Nice, %s was saved to the database.'), $example_name));
			}
		}
		// Edit example and load data for example.
		if (!empty($edit_mode) && !empty($ee)) {
			// If item is in edit mode.
			$ex_array = $this->db->invokeQuery('ExamplePlugin_editExampleQuery', $ee);

			$id = $ex_array['id'];
			$example_name = $ex_array['example_name'];
			$example_note = $ex_array['example_note'];
			$alias = $ex_array['alias'];
		}

		// Load views plugin.
		$view = $this->factory('views');

		// Set Values.
		$view->set('self_url', $this->navigation->selfUrl());
		$view->set('id', $id);
		$view->set('example_name', $example_name);
		$view->set('example_note', $example_note);
		$view->set('alias', $alias);

		// Output to view.
		$view->show();
	}
}

return 'manageExample';
As always, PHPDevShell will never try to force you to use its models system. You can do direct queries from within the controller. To do a direct query you simply:
$query = $this->db->newQuery('SELECT * FROM _db_core_users');
// Or use its alias method which does exactly the same thing
$query = $this->db->sqlQuery('SELECT * FROM _db_core_users');

In above example it is seen how the views and models are being done inside the controller.

Database model conversion

This page is about the database conversion in PHPDevShell (Plugin) to move all integrated queries to its relevant models.

Some things you should note:

  • Each .../controller/filename.php has a model in .../models/filename.query.php
  • Each query has its own class in its relevant file (several queries can be found inside the same model file)
  • Each query class can and may have basic logic in conjunction with the query

What needs to happen is the queries needs to be split from the script and inserted into a query class in the models folder. There are basic classes available now to assist in managing the queries more effectively.

In the root folder look at /includes/PHPDS_query.class.php for detail into the available methods to assist in shortening the query class.

For this tutorial we will use /plugins/PHPDevShell/scripts/user-role-admin.php; its queries will be in /plugins/PHPDevShell/models/user-role-admin.query.php.

As a first step, we just need the query to contain the sql text:

class PHPDS_readRoleUser_query extends PHPDS_query
{
	protected $sql = '
		SELECT
			user_role_id, user_role_name, user_role_note
		FROM
			_db_core_user_roles
		WHERE
			user_role_id = %u
	';
}

As it, any invocation of this query will use the first parameter as the searched role ID:

$select_user_role_array = $this->db->invokeQuery('PHPDS_readRoleUser_query',$this>security->get['er']);

Provided $this->security->get['er'] is correctly filled, the result will be something like:

Array (
	[0] => Array (
		[user_role_id] => 2
		[user_role_name] => "Registered"
		[user_role_note] =>;
	)
)

Note it's a one-value array containing the desired data. It would be preferable to get this data directly. Just add a line to the query:

class PHPDS_readRoleUser_query extends PHPDS_query
{
	protected $sql = '
		SELECT
			user_role_id, user_role_name, user_role_note
		FROM
			_db_core_user_roles
		WHERE
			user_role_id = %u
	';

	protected $single_row = true;
}

However, the concept of a query is to minimize the parameters in order to simplify the calling function. So we'll make the role ID optional; for that, we just check if the first parameter is empty, and if so, give it the proper value.

class PHPDS_readRoleUser_query extends PHPDS_query
{
	protected $sql = '
		SELECT
			user_role_id, user_role_name, user_role_note
		FROM
			_db_core_user_roles
		WHERE
			user_role_id = %u
		';

	public function check_parameters(&$parameters)
	{
		if (empty($parameters[0]))
			$parameters[0] = $this->security->get['er'];
		return true;
	}

}

Now I can use my query that way:

$select_user_role_array = $this->db->invokeQuery('readRoleUser');

If for some reason I want the user role data for another role ID:

$select_user_role_array = $this->db>invokeQuery('readRoleUser', $anotherRoleID);

Note about this example: don't forget to call $this->security->load_security(true) at the beginning of your script or get['er'] will be void.

Sequence

The default sequence of calls is as this:

invoke()
check_parameters() -- step 1: checking in

query() -- step 2: querying
build()
prebuild()
sql()
extra_build()
parameter injection
actual db querying
as_whole()

check_results() -- step 3: checking out

You can clearly see the 3 steps: 1/ checking in, 2/ querying and 3/ checking out. Use this to decide what you want to override. In our example, only in-parameters needed our attention. In another case, let's say I want to build an html table out of the results, I could only override check_results().

What should I override?

The conception of PHPDS_query is based on the following idea:

  • provide building-blocks for query handling
  • provide default behavior corresponding to most usages
  • provide placeholders (callbacks) to handle most custom cases

You'll probably never override the building blocks, rarely override the default behaviors, often override the placeholders (they are meant for that).

Placeholders

In the base implementation, these methods don't do anything. They are meant to be overriden :

  • public function check_parameters(&$parameters = null)
  • public function check_results(&$results = null)

You can there check and change what's coming in (parameters) and out of (results) the query. Return true to let the process or false to break (in that case the query will return false; you can also raise an exception).

Default behaviors

Using the building blocks there are several methods which calls them corresponding the general use. You can see them as an orchestra master. You can override them if you want a very specific melody.

  • public function invoke($parameters = null) : top-level score (checking parameters, building and doing the query, getting and checking the results)
  • public function prebuild() : create the SELECT ... FROM clause if needed
  • public function build($parameters = null) : build the query sql text (prebuild, sql, extra_build - these are useful when the sql text is actually written by the class)
  • public function extra_build($parameters = null) : create the WHERE/GROUP BY/ORDER BY clauses if needed

If you need to add some special logic, be sure to call the parent:

public function invoke($parameters = null)
{
	$result = parent::invoke($parameters);

	// my logic here

	return $result;
}

Forms and Crud

article_crud_frontcover.jpg

PHPDevShell provides a complete yet easy extendible Forms and CRUD plugin to deal with Forms in a clean and neat way. There are always some cases where you would need to deviate and have a custom form manager. However, for all general forms, the PHPDevShell Form & CRUD will be more than enough. In later articles it can be made even easier with the build in ORM.

Many know just how tricky and limited Forms can be when creating a general Form controller to make development life easier. We came to the conclusion that Form and CRUD support should not be a do all end all solution, just a supportive tool. In summery;

  • Dont auto-generate forms, HTML is easy enough to write forms by hand leaving full control with layout and parameters to the developer. And besides learning code to write forms with while you already know HTML is pretty unproductive.
  • You need a CRUD to make the FORM data ready for saving after validating without affecting the standard HTML you created the FORM with.
  • Option type fields should be easy to keep selected or be selected when editing.
  • Using standard queries for saving or using ORM to save FORM data should be mostly the same.
  • You should not have to define variables when the form is not yet submitted.

When you are done with this tutorial you will have a full working form;

Screenshot - 09022012.png

01So the formula and rules for our Forms system seems pretty straight forward, lets dive right into our controller and begin the form development. For this tutorial we will be using MVC to keep things as need as possible. Lets start with our example and call the required CRUD plugin.

controller file : plugins/ExamplePlugin/controllers/manage-example.php

<?php
class manageExample extends PHPDS_controller
{
	public function execute()
	{
		// Note the comment format, this is to enable IDE auto completion.
		/* @var $crud crud */
		$crud = $this->factory('crud');

		// Some application information.
		$this->template->heading(_('Example Simple Form'));
		$this->template->info(_('On this page we will show you how to do a simple form and save data to the database using Models. We will use Models instead of ORM here. There are other examples of easier forms using ORM.'));

	}
}
?>

02
Ok now that our controller is running, lets start working on the view and create the form using standard HTML. You will also note I will be adding the variables inside the HTML so that when editing or submitting the values should be filled in and the correct options should be selected.

controller file : plugins/ExamplePlugin/views/manage-example.tpl

{if $id != ''}

{/if}

We wont see the form display just yet, we still need to tell our controller about the view and setup the validation and everything. So lets complete our controller that we started with above.

Check out the crud API generated docs to see all the validation options available to you.

Let me finish the controller and add as many comments as possible, I will highlight some points afterwards;

controller file : plugins/ExamplePlugin/controllers/manage-example.php

<?php

/**
 * Controller Class: New example and edit example.
 * Like always we start our node with the controller.
 * @author Jason Schoeman
 * @return string
 */
class manageExample extends PHPDS_controller
{

	/**
	 * Execute Controller
	 * @author Jason Schoeman
	 */
	public function execute()
	{
		// Load CRUD to make form validation slightly easier...
		/* @var $crud crud */
		$crud = $this->factory('crud');

		// Do we have an id, this we check if url contains an &id=12 for editing for instance.
		if ($crud->REQUEST('id'))
			$crud->f->id = $crud->REQUEST('id');

		// Lets create header information.
		$this->template->heading(_('Example Simple Form'));
		$this->template->info(_('On this page we will show you how to do a simple form and save data to the database using Models. We will use Models instead of ORM here. There are other examples of easier forms using ORM.'));

		// If item gets posted, lets read it, validate it and save it to the database.
		if ($crud->POST('save')) {

			// For instance, name must be Alpha letters only, else write to fail log with message.
			if (!$crud->isAlpha('example_name'))
				$crud->error("This field cannot be empty and may not contain spaces");

			// For instance, must meet minimum lenght of 20 characters.
			if (!$crud->isMinLength('example_note', 20))
				$crud->error("This is too short, it needs to be at least 100 characters");

			// All must be lower case.
			if (!$crud->isLower('alias'))
				$crud->error("Alias can only be lower case letters");

			// Multiple options require a seperate table to store values, lets treat this now.
			if (!$crud->isMultipleOption('example_multi_select_crud'))
				$crud->error("Please pick at least one option");

			// If everything is ok and we have no errors written in $crud->error we can continue.
			if ($crud->ok()) {
				$crud->f->id = $this->db->invokeQuery('ExamplePlugin_writeExampleQuery', $crud->f->id, $crud->f->example_name, $crud->f->example_note, $crud->f->alias);
				// Show item updated.
				$this->template->ok(sprintf(_('Nice, %s was saved to the database.'), $crud->f->example_name));
			} else {
				$this->template->notice('Form was not saved due to errors.');
				// We can now show the errors and mark all fields automaticall... so simple, so clean, so much fun.
				$crud->errorShow();
			}
		}

		// Edit example and load data for example.
		// This is when we have an id var in the url and we want to show and edit the data.
		if ($crud->GET('id'))
			// The import fields is a nice way to write the results of the query array to the $crud tables $crud->f fields.
			$crud->importFields($this->db->invokeQuery('ExamplePlugin_editExampleQuery', $crud->GET('id')));

		// Load views plugin.
		$view = $this->factory('views');

		// Just some example options.
		$options = array(1=>'Option 1', 2=>'Option 2', 3=>'Option 3');

		// Set Values for Views variables.
		$view->set('self_url', $this->navigation->selfUrl());
		$view->set('id', $crud->f->id);
		$view->set('example_name', $crud->f->example_name);
		$view->set('example_note', $crud->f->example_note);

		// For multiple option we can take this a step further...
		$view->set('example_multi_select_crud', $crud->select($options, $crud->multiSelected('example_multi_select_crud', $crud->f->id)));

		$view->set('alias', $crud->f->alias);

		// Output to view.
		$view->show();
	}
}

return 'manageExample';
When using $crud->POST(), $crud->GET(), $crud->REQUEST() the form data is automatically protected against SQL injection.

As you can see, the controller looks pretty neat and clean, we can read it easily. But there are a we things I would like to clear up.

Here we just check if the user is editing a form or if the form was submitted, we want the data not to clear after submit, the id field is a good starting point. All fields are assigned to $crud->f property, this property ensures that the field is not undefined when used before data is available and also servers as a data field collector.

		if ($crud->REQUEST('id'))
			$crud->f->id = $crud->REQUEST('id');

There are over 50 validation methods available for your fields, each time you use a validator method the field automatically gets assigned to $crud->f too, making the field name data available immediately, like $this->f->example_name

Here we check if there was any $crud->error() messages that succeeded, if there was any, it would fail and the $crud->errorShow() methods does the work of failing the form and showing the error message, now work from you!

		if ($crud->ok()) {
				$crud->f->id = $this->db->invokeQuery('ExamplePlugin_writeExampleQuery', $crud->f->id, $crud->f->example_name, $crud->f->example_note, $crud->f->alias);
				// Show item updated.
				$this->template->ok(sprintf(_('Nice, %s was saved to the database.'), $crud->f->example_name));
			} else {
				$this->template->notice('Form was not saved due to errors.');
				// We can now show the errors and mark all fields automaticall... so simple, so clean, so much fun.
				$crud->errorShow();
			}

To make life easier when you have selection boxes and a standard database table for the selection (could also be a give array), you can use one line to show the option and default to the selected one with this line :

$options = array(1=>'Option 1', 2=>'Option 2', 3=>'Option 3');

$view->set('example_multi_select_crud', $crud->select($options, $crud->multiSelected('example_multi_select_crud', $crud->f->id)));

$crud->select($available_options(array), $selected_options(array)) just draws the options, where $crud->multiSelected() returns an array of selected values for this form, remember it can be multiple. Note how the database structure look for above multi selected options:

					CREATE TABLE `_db_example_multi_select_crud` (
					  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
					  `join_id` tinyint(3) unsigned DEFAULT NULL,
					  `value` tinyint(3) unsigned DEFAULT NULL,
					  PRIMARY KEY (`id`)
					) ENGINE=InnoDB DEFAULT CHARSET=utf8;

03We are not done yet, we need to create the model and sql data which is called by $this->db->invokeQuery(), the database model file normally consists of a set of classes and its queries, lets create it now:

controller file : plugins/ExamplePlugin/models/manage-example.query.php

/**
 * This model will save new data to database.
 * @author Jason Schoeman, Contact: titan [at] phpdevshell [dot] org.
 *
 */
class ExamplePlugin_writeExampleQuery extends PHPDS_query
{
	protected $sql = "
		REPLACE INTO
			_db_ExamplePlugin_example (id, example_name, example_note, alias)
		VALUES
			(%u, '%s', '%s', '%s')
    ";
	protected $returnId = true;
}

/**
 * This model will show data when we are editing.
 * @author Jason Schoeman, Contact: titan [at] phpdevshell [dot] org.
 *
 */
class ExamplePlugin_editExampleQuery extends PHPDS_query
{
	protected $sql = "
		SELECT
			id, example_name, example_note, alias
		FROM
			_db_ExamplePlugin_example
		WHERE
			id = %u
    ";
	protected $singleRow = true;
}

Conclusion

It might seem slightly daunting at first, however, once you get used to it, it is quite simple, remember to check out many form examples in the PHPDevShell plugin and ExamplePlugin, you should be getting along with it in no time at all!

DB Configuration Registry

article_normal_settings.jpg

In any application at some stage you would want to add dynamic configuration options per installation. This should be easy to change from a front-end ui and easy to collect from the development side. PHPDevShell offers a very quick and fast way to collect configuration data for a specific plugin. It allows you to enter data and retrieve it from the development side.

With this tutorial we will assume that you have a working plugin, if you don't have a look at the ExamplePlugin for a working version of this tutorial.

01Lets add a few things to the configuration repository, to do this, simple open PHPDevShell UI and browse to System Management->Config Manager, now we will add a setting or two.

When adding your setting for your plugin, always add the plugin name as the prefix, something like this ExamplePlugin_, this tells PHPDevShell where exactly to find the settings and also eliminates same names used. So for instance, so lets add the following settings;


Setting Name : ExamplePlugin_sampleSetting1
Setting Value : Just some random data.
and
Setting Name : ExamplePlugin_sampleSomethingElse
Setting Value : Another setting example

02Now that you see both setting in the settings repository, go to your controller script and call them. Note that you will be using the ExamplePlugin that you used in the prefix to call your plugin. If the second value is left empty the system will assume the current running plugin as the prefix.

$setting = $this->db->getSettings(array('sampleSetting1', 'sampleSomethingElse'), 'ExamplePlugin');

You have now requested the system to call the two values from the settings registry and store them into an array, to use them now is quite easy;

echo $setting['sampleSetting1']; // Will echo "Just some random data."
echo $setting['sampleSomethingElse']; // Will echo "Another setting example"

You have now learned the basics of using the configuration registry.

Improving ease of use

At a later stage, you might want to build a UI specific for a plugins settings, especially if noob admin staff will change settings. This is quite easy to do as there are two more methods you will have available to easily store and update data. These are;

$this->db->writeSettings(array('sampleSetting1'=>'value', 'sampleSetting2'=>'value2'),'ExamplePlugin');
$this->db->deleteSettings(array('sampleSetting1', 'sampleSetting2'), 'ExamplePlugin');

This concludes the basic usage of the configuration registry, now it is up to you and your imagination on how you could use this in a productive environment to make your plugin more sustainable.

Widgets, Lightbox and Ajax

article_Cool-Sexy-Ajax.jpg

PHPDevShell offers and exceptional powerful way to make your website or application as fluent as possible with the use of powerful Ajax calls. It is made very simple and you will be able to do an ajax call, create a widget or lightbox popup within minutes.

 

 

The greatest of it all is, you create any of the above exactly like you would create any other controller, you dont need to be an expert in Javascript, you just need to code in PHP and call it via an ajax method you choose (we have an easy to use build in ajax call via Jquery too). So in the end, your ajax page could look like any other controller, the system will strip out HTML thats not needed.. Make sure you understand how to create a normal controller before you continue with this tutorial.
It is important to note that the ExamplePlugin shipped with PHPDevShell has examples of all that is discussed below.

Different Menu/Node Ajax Types

We will be looking at the different node types available first, they are very simple to use and generally gets the job done within minutes.

When any Widget, HTML Ajax or Lightbox is called and you did not set its correct menu permissions, it will not show.

(9) HTML Ajax Widget Module (Bordered)

The widget node type creates a nice mini page inside an existing page, which is generally bordered and themed like your main theme. Widgets can be seen as the side blocks/modules of a normal web page.

First we will be creating the controller (plugins/ExamplePlugin/controllers/widgets/simple-widget.php) for the widget, even if you have a widget type you can still use the exact same MVC structure as with any other node.

class widget extends PHPDS_controller
{
	/**
	 * Execute Controller
	 * @author Jason Schoeman
	 */
	public function execute()
	{
		// Load views plugin.
		$view = $this->factory('views');
		
		// Lets do something silly with this widget... like show a logged in as and logout link.
		$name = $this->configuration['user_display_name'];
		$logout_page = $this->navigation->buildURL('3682403894', 'logout=1');
		
		// Receiving some extra data, this is used, 
		// meaning whatever you pass as the source url for the ajax call
		// can be captures like here.
		$data = $this->GET('data');
		$moredata = $this->GET('moredata');
		
		// Pass vars to the view.
		$view->set('name', $name);
		$view->set('moredata', $moredata );
		$view->set('data', $data );
		$view->set('logout_page', $logout_page);
		
		// Output to view.
		$view->show();
	}
}

return 'widget';

If you understand how to create normal controllers above should be a breeze, you should also note that the idea is for you to add models normally too, so queries can be done like with normal nodes or the way you prefer. Lets create the view (plugins/ExamplePlugin/views/widgets/simple-widget.tpl) now for the controller above;

You are logged in as, {$name}

I have received passed data from main controller: {$data}, more data: {$moredata}. Widgets are used to create mini applications inside a main web page.

Obviously all web elements is available. Widgets are created the exact same way as normal nodes.

Now we are done with a basic application for PHPDevShell, you should note that this might as well be a normal plugin menu type (1) which could run as a normal page, this is how perfectly similar they are.

Calling it from the main controller via Ajax

PHPDevShell offers a few standard ways to call ajax without the need to use any javascript, you could of-coarse use your own, but most of the times the generic calls will do just fine. So in the next step we will be looking at calling it from our main controller to display it in a main page. For this you will need the menu id for the widget created above (find this under the menu manager).

Now from the page where you want to display the widget within, just add the following php string in your controller. Note that you can call multiple widgets.

// @field1 The menu id (widget) you want to call (make sure permissions is set).
// @field2 The element id that should be replace with the widget.
// @field3 Get data you would like to submit with the ajax request to be captured in your widget controller.
$this->template->requestWidget('2282118247', 'widget1', 'data=Hello World&moredata=Foobar');

Wow, we are almost there, now we just need to add a div tag with our widget1 id so the system knows where to place the widget.

	

This side we call a widget through ajax :D

(10) HTML Ajax Call (Styled)

This menu type is the same as a widget in all regards, except it does not draw any borders or widget like styling. It is styled and is used to request html ajax. It is developed exactly the same way as you would create a widget, except it is called with;

$this->template->requestAjax('1821693117', 'ajax1', 'data=FooBar');

(11) HTML Ajax Lightbox (Overlay)

Will load request a popup overlay ajax html lightbox to display content in. It is created exactly like a normal widget or page but is called with;

$lightboxurl = $this->template->requestLightbox('1133107805', 'lightbox', 'data=Lightbox Foobar');

The HTML will look slightly different, lets use the url to create the lightbox link.

Click here to see lightbox in action

(12) RAW Ajax Call (Json, XML, text, etc.) (PHPDevShell V3.1.4+)

This is also created like any other controller or menu item (except we need to set the header type for the data it will return), except it does not style anything and is meant for calling raw data. There is no included php method to call this type of node. Because it is returns purely what the developer outputs in his controller and excludes all styling an html. It is also a lighter call as it skips the whole template engine. Lets create a live searchbox for instance, we wall add the javascript in our main controller and call the raw ajax node type remotely, which in turns does a quick query and returns the results.

Lets look at how a simple Raw Ajax node type controller could look and can be used for a live search field;

class ajax extends PHPDS_controller
{
	/**
	 * Execute Controller
	 * @author Jason Schoeman
	 */
	public function execute()
	{
		// We need to tell jquery what data type we will be sending back.
		PU_silentHeader("Content-Type: application/json");

		// Lets play around with RAW ajax.
		// In this example we will do a simple query to check if we have any data requested.
		if ($this->G('term')) {
			$data = $this->db->invokeQuery('ExamplePlugin_menuAjaxQuery', $this->G('term'));
			// Ok lets loop it and create a json string.
			if (! empty($data)) {
				$json = '';
				foreach ($data as $name) {
					$json[] = array('name'=>$name['menu_name']);
				}
				print $this->G("callback") . "(" . json_encode($json) . ")";
			} else {
				print '()';
			}
		} else {
			print '()';
		}
	}
}

return 'ajax';
It is important to remember that you will need to write your own Javascript to call a Raw Ajax node type, this is too specialized to have a generic PHP function for.

a Full working example of above is in the Example Plugin.

Using Ajax by calling same controller

At some point you might not need to call ajax data from another node, you might have some small ajax call which could be written in the same controller as the where it is being called from, PHPDevShell makes it possible to be calling the same controller and having ajax data in a specialized viaAjax method, only when ajax is called.

When the same controller is called using ajax, PHPDevShell is smart enough not to load the execute() method of the original controller but to skip this completely and load a viaAjax() methods which in turn is used to eco ajax data.

This smart Ajax technology can be used for three different things:

  • dynamically loading a page or part of a page to inject the responded HTML into the DOM using the same controller.
  • calling the same controller and getting JSON/XML as an answer
  • call a PHP function from a JavaScript

Of course all three involve using JavaScript, but PHPDevShell tries to make as painless as possible.

Dynamic HTML loading

Let's study this example:

in our page, we have two blocs which should be refreshed with ajax; the content is created by 3 methods: makePage() creates the whole page, and makeBlock1() and makeBlock2() fill the blocs content.

The first step is straightforward:

class myPage extends PHPDS_controller 
{

            public function makePage() {
                /* fill the page content */
                $this->makeBlock1();
                $this->makeBlock2();
            }

            public function makeBlock1() {
                /* fill the content of the first block */
            }

            public function makeBlock2() {
                /* fill the content of the second block */
            }

            public function execute() {
                $this->makePage();
            }

}

So, when the user requests "myPage.html" in the traditional the whole page is created and sent back for the browser to display.

Now, how can I easily load only one block? Let's say I configured my javascript to request "myPage.html?block=1" to fetch the first bloc and "myPage.html?block=2". In normal cases if we had to load the the same page execute() would run, but because PHPDevShell is smart and you call the exact same page, but this time with ajax, the viaAjax() method will be called and execute() will be ignored. So now we just have to add a new method to my class:

class myPage extends PHPDS_controller 
{
	public function makePage() {
		/* fill the page content */
		$this->makeBlock1();
		$this->makeBlock2();
	}

	public function makeBlock1() {
		/* fill the content of the first block */
	}

	public function makeBlock2() {
		/* fill the content of the second block */
	}

	public function execute() {
		$this->makePage();
	}

	// This method will be called only when an ajax call is automatically detected.
	public function viaAjax()
	{
    		switch ($this->GET('block')) {
        	case 1: makeBlock1(); break;
        	case 2: makeBlock2(); break;
    	}

	return false;
}

That's all. When the page is called via ajax, the method is called. Based on the GET parameter, I just output the content of the corresponding block. I could have done that in the main method (execute() ), but it could have been called via traditional method, and I want to be sure the blocks can be fetched only by ajax.

JSON's return

Ajax can also be used to retrieve raw data, i.e. not HTML. Usually, the data is encoded in either JSON or XML. The script is then seen as a function with parameters and a result.
Implementing this with PHPDevShell is very easy; let's pretend we want to implement "multiply a by b":

public function viaAjax()
{
    $a = intval($this->GET('a'));
    $b = intval($this->GET('b'));

    return ($a * $b); // it will be automatically json-encoded
}

Note that the data is explicitly encoded to json; it will be then send back to the browser as such (no template). If the browser adds a special header "HTTP_X_REQUESTED_TYPE", the result will be automatically encoded.

Remember to check out the examples in the ExamplePlugin that is shipped with PHPDevShell, it contains all these examples in one plugin to examine and see it in action.

Dependant and Dependency Injection

You may have not notice most of the new style classes in PHPDevShell are daughters of PHPDS_dependant, and you may ask why. This document will try to explain that.

Why dependency?

When PHPDevShell was moved from procedural to OOP, we ran into the problems of global variables. For example, the connection to the database is initiated at the beginning of the cycle, but how can a script refer to this connection when it runs? we need to give you, the plugin developers, a very simple way to refer to all objects you need when your script is executed.

How is it done?

The solution we found was to create a main instance, on which the core classes were connected. We call this "root tree" the Core. From then, when a new object is instantiated, it is also connected to the Core thought dependency injection. To handle this is the easiest way possible, we created a "base" class which can handle this dependency injection for you: PHPDS_dependant.

So instead of using the new() operator, which gives a lonely object, you use the factory() method:

$new_object = $this->factory('my_class');

This method creates the object via new(), then calls dependance($caller) on this new object giving the calling object as the parameter, allowing the new object to obtain a link to its caller, and therefore to the caller's link, which ultimately is the Skel. From that time, the new object can access all the framework services, through this dependency link.

Getting to the core services

All the core classes connected to the Skel can be access via a trick; for example, if I want to access the cores PHPDS_db object, I can just write:

$db = $this->db;

The following services is available anywhere:

$this->core 
$this->db
$this->navigation
$this->user
$this->security
$this->tagger
$this->template
$this->configuration

So basically this means that I can access all shared core information, methods and data, with a simple direct call. Lets get the current logged in user id and name for instance:

echo $this->configuration['user_id'] . '
'; echo $this->configuration['user_name']

Or lets call a method from a class:

$this->db->newQuery('SELECT * FROM table');

Look at core methods here: http://doc.phpdevshell.org/hierarchy.html

NOTE: in the current implementation, the "owner"" (i.e. the calling object) is sent, as the injected dependency, both as the first argument of the constructor, AND just after via the my_dependancy() method. However, the good practice is too use the alternate constructor construct() instead of the language's __construct().

Plugin Class Sharing and Substituting

PHPDevShell offers a neat way to substitute a plugin with your own version. This is very useful where on some or all pages you would like different functionality then what a plugin provides.

Plugin Public Classes

Many plugins provides a class api to reuse, these classes gets registered in the class registration table to be called with the factory method at any time.

$somePluginClass = $this->factory('ClassName');
$somePluginClass->doSomething();
Remember that you register a class using the plugin.config.xml with tags:
<class name="exampleClass" alias="EXAMPLE_exampleClass" plugin="ExamplePlugin" rank="last">
</class>

See plugin config for more details.

You will note that a plugin class have two ways of calling its classes, you can use its name or alias as registered. This allows you to use different types of calls depending on what your page requires. I also allows you to have two of the same plugins active to extend functionality on specific pages that each might require differently.

Duplicating a Plugin

Once you decided what plugin you would like to change, simply copy the plugin, give it a new name and paste it back into the plugins folder. You need to edit the plugin.config.xml to appropriately represent your modified plugin.

For this tutorial we will use an actual plugin available in the distribution package and reusing it. Lets look at the userActions plugin, all this plugin does is hook into any action that is performed using the standard PHPDevShell UI. Actions like when a user gets added, deleted, imported updated anywhere. This plugin features a bunch of empty methods for you to use as they are being called by default.

The first step is going to be to copy the plugin and renaming it. Then we will make some minor changes to the config.plugin.xml file to make it work;

  • Copy and rename the userActions back into plugins folder. I called mine userExample.
  • Then open the config file in plugins/userExample/config/plugin.config.xml and change;
FROM:
<name>userActions</name>
TO:
<name>userExample</name>
AND FROM:
<class name="userActions" alias="PHPDS_userActions" plugin="userActions" rank="last" />
TO:
<class name="userActions" alias="EXAMPLE_userActions" plugin="userExample" rank="last" />

This is basically all we need to change in the config for this plugin. This plugin only has one class, however multiple classes are supported where single once can be used, or all of them. For the simplicity of this tutorial we will only use the one available class for this plugin.

All plugins classes are available in the plugins/userExample/includes/ folder. The class name is usually the same as the filename, in this case, userActions will be includes/userActions.class.php, the class name would look like this:

class userActions extends PHPDS_dependant
{
	// Methods...
}

Ok so this class has very self explanatory methods, but lets do something else when a user gets deleted on PHPDevShell.

class userActions extends PHPDS_dependant
{
	public function userDelete ($userArray)
	{
		$this->template->notice('Some extra actions done here for ' . $userArray['user_name']);
		print_r($userArray);
		// You can code with this data whatever you want. Delete from LDAP for example.
		// You can extend it to delete from another database table for instance.
		// Do whatever you want with this deleted action.
	}
}
Remember that when overriding a plugin class, all methods must be available in the new plugin, all the properties must also be the same.

Assuming all methods from old plugin is now in new plugin, we will be ready to enable this plugin and start using it. Open PHPDevShell UI and go to System Management->System Admin->Plugins Admin, you will note that userExample is now listed under plugins. It is not installed yes, to install, next to the userExample plugin, click on the install plugin button.

Your new modified plugin is now ready to use. Lets test it by creating a new user under the UIs User Management and deleting the user again. You should see your custom message with the array of data being printed out.

As you might notice, this is extremely easy to use, it offers allot of possibilities. In real life you would use the userActions with the StandardLogin plugin to create a whole new login mechanism or bridge to another system.

To recap what just happened, you should remember that anywhere, where a plugin is called with the name "userActions" (for this example) the new methods of your new plugin will be used instead of the original, so above methods userDelete will be replaced with the new method everywhere this is used in code:

$somePluginClass = $this->factory('userActions');
$somePluginClass->userDelete($userArray);

In this methods case, above code was placed everywhere in the PHPDevShell plugin where a user gets deleted.

Remember that your main plugin can have supportive plugins. This will allow other users to extend functionality on your own plugin. Meaning everywhere in your own code, where you call a plugin and it methods, this will be able to be replaced with another plugin.

Substituting the Class with a new Class name

Lets assume we have the following problem. We want to use a different class name then 'userActions', however, $this->factory('userActions') is hardcoded everywhere already, we cant change this? Well there is a proper solution for this. All you need to do is the follow, in your newly created plugin your config should look something like this;

		<classes>
			<class name="userActionsCustom" alias="userActions" plugin="userActionsExample" rank="last" />
		</classes>

In this case, alias is the alias to the orginal userActions class, meaning wherever we called $this->factory('userActions') the system will detect the alias of the new plugin. And what is the alias refers to class name="userActionsCustom". So the system will replace the orginal userActions for plugins/userActionsExample/includes/userActionsCustom.class.php. The content will look something like this:

class userActionsCustom extends PHPDS_dependant
{
	// Same methods, new code here.
}

Using the alias to call a method from original plugin class and not from the substitution class

Alias can also be an alias tag to an original method. Lets study the following problem. I am happily using my substitution plugin throughout the system. But for some reason I need the method of an original plugin that I already substituted. This is quite easy to overcome. Again the alias can be used in your own factory call to use original methods. Lets look at the following original plugin config;

<class name="userActions" alias="PHPDS_userActions" plugin="userActions" rank="last" />

To use a method from this class and not the substitution class, I simply do this:

$this->factory('PHPDS_userActions')->userDelete();
In your config, class name="" always refers to the class file name meaning example: userActionsCustom.class.php and the actual class name meaning example: class userActionsCustom extends PHPDS_dependant. This principle never changes, always have the same class name as defined in name="".

Alias Conclusion

In short, it can be seen that alias can either be used linking an original class to a new class name. Or alias can be used to use an original method from an original class if the class was substituted with a new class.

Plugin class instance sharing and singleton

In some cases you will most probably want to share (singleton) an existing instance of a class instead of creating a new instance of the class. This is very possible and an instance can be shared from any other script that exists. Meaning you can use the same instance from withing a model and a controller file.

All of this is probably best explained in code:

$test = $this->singleton('Station');
echo $test->methodTwo() . '
'; // Print 0 echo $test->methodTwo() . '
'; // Print 1 echo $test->methodTwo() . '
'; // Print 2 $extend = $this->singleton('Sharing'); print $extend->someOtherMethod() . '
'; // Print 3 class Station extends PHPDS_dependant { protected $count = 0; public function methodTwo() { return $this->count++; } } class Sharing extends PHPDS_dependant { public function someOtherMethod() { $instance = $this->singleton('Station'); print $instance->methodTwo(); } public function sharedResources() { // Everything available. $this->core->formatDateTime(); } } $a = $this->factory('Station'); $b = $this->factory('Station'); $c = $this->singleton('Station'); $d = $this->singleton('Station'); // Note & before the class name, this tells the system it should be singleton. $e = $this->factory('&Station'); print '$a = $b ? '.($a === $b ? 'yes' : 'no').' (unique)
'; print '$a = $c ? '.($a === $c ? 'yes' : 'no').' (unique)
'; print '$c = $d ? '.($c === $d ? 'yes' : 'no').' (instance)
'; print '$c = $e ? '.($c === $d ? 'yes' : 'no').' (shortcut to instance)
';



Writing a good phpds class

As a web application, PHPDS' way of working is in cycle: it starts from scratch when the web server receives a request, grows like a tree, and then disappear. You can say the framework handles all the wood grows, and you just focus on the leaves and flowers.

However every leave is hooked on a branch. That means your class must conform to a few rules in order to be integrated.

First, it must be a daughter class of PHPDS_dependant (defined in /includes/PHPDS.inc.php).

Second... well that's it for the definition:

class myClass extends PHPDS_dependant
{
	public function construct($myParam1, $myParam2 = 'default')
	{
		// do what a constructor should do
		....
	}
}
Notice the lack of double underscore before the method name: it's not the real constructor, it's called by the constructor after injecting the dependance.

That means:

  • it's running in a constructor context (don't do anything not allowed for constructors)
  • the dependance is complete (you can use $this->factory() or $this->configuration for example)
  • you don't have to bother with the $dependence parameter: the parameters are the parameters given to factory()

When you need to instantiate your class, just use the factory method:

$myObject = $this->factory('myClass', 'value of myParam1');

If your parameters are available as an array (instead of direct list of $variables), use the factoryWith() method:

$myParams = array('value of myParam1');
$myObject = $this->factoryWith('myClass', $myParams);

The factory will handle sneaking, instantiating, and dependency injection. For information, the $dependance parameter is given to the constructor because you might want to do at this step something requiring the dependency. However, after the object is constructed, the injection is made anyway.

Note: I wrote $this here because it's the usual way of calling it in a PHPDS script context. However you can use any object which has been previously injected.

BotBlock protecting public forms against spam

article_No Spam.png

(PHPDevShell 3.1.2+) You will most certainly come across your developed form being spammed to death by some random bot who started harvesting your forum and found a public form to submit junk to. Sure it can be eliminated with captcha, however, we find this very unprofessional on simple forms. You also probably learned by now that most end users have very low IQ, finding the captcha almost impossible to complete.

In the end it should not be the end user who gets punished because of a stupid bot. This is I developed BotBlock for PHPDevShell. It monitors public form posting behaviors and reacts if it is suspected of being a spam bot. It does various behind the scene tests which will stop 99% of all automated bots without interfering with the form or expecting user input.

To use is very simple, it will be implemented inside your controller. For an example look at PHPDevShell plugin, inside controllers/user/email-admin.php. First thing we will be doing is to call the class;

/* @var $spam botBlock */
$spam = $this->factory('botBlock');
Note the comment above the code, if you are using Netbeans this will allow you to have auto completion for any callable class through factory simply by adding that comment.

Next we will need to add the spam stop and check part, this part needs to be added after the rest of the form approved the content and the submitted form is now ready to be processed.

if ($crud->POST('send_mail')) {
	if (!$crud->isEmail('email_from')) $crud->error(_('Please provide a valid email address'));
	
	// The last check should be spam by using $spam->block()
	if ($crud->ok() && $spam->block()) {
		// Send email and save entry.
	}
}

This is not all, we have another trick up our sleeve to trick the bot with, this is by means of fake fields. The fake fields gets hidden and passed fails the form as soon as the bot completes them. To activate this additional check simply pass this inside your forms tags.

	// $fake_fields will contain the extra fields and will automatically hide it.
	$fake_fields = $spam->botBlockFields();

	// You can now pass it to your views.
	$view->set('fake_fields', $fake_fields);

	// Remember to create a {$fake_fields} in your Smarty template between your forms tag. 

Now you have an excellent silent bot block monitoring your forms.

HerculesPostman Mass Mailing List Plugin

article_Screenshot-4.png

HerculesPostman is an effective and extremely easy to use mailing list manager designed to send advanced designed mail for as many users as your server can handle.

HerculesPostman was designed with marketing members in mind. We all know in the end they will be using it, hence true focus was put on its usability and objectives.

When your company requires Mass Mail services to contact clients, you will quickly learn that many systems is either very expensive or quite mediocre. Or you might find that they are overly complex and not quite what you require. I experienced this too and decided to do my own that fill my needs and that is easy enough to be used by admin people. Hercules is the result of a well thought through simple, yet very powerful mass mailing solution.

Hercules is full LGPL open source, meaning you can do with it what you like, including redistributing it. Please support us, if you find this application useful, consider making a donation here.

!You can download Hercules here!
Thank you for your support!

heading_Screenshot_1.png

Features

  • Runs on PHPDevShell and its users.
  • Unlimited users or emails.
  • Send email per group.
  • Support most email server types.
  • Support for any email template.
  • Attachments and inline images.
  • Auto resize and image insert.
  • PDF to Image conversions and auto insert.
  • Embedded images.
  • Mailing list queue with status reports.
  • Adjust From and Reply address.
  • Full Wysiwyg editor.
  • Easy upload for body design images.
  • HTML/Text Emails.
  • Programmable delivery date.
  • Unsubscribe and Browser View for mail being send out.

Requirements

For Hercules to run successfully you will need a running version of the latest PHPDevShell and you should be able to send out a test email from the General Settings gui screen.

For mass mail (hundreds of users) you will need to have cron activated on PHPDevShell to run every three minutes.

Note that Hercules was only tested on Linux. Windows servers might not have the required components to make Hercules run without problems. You are welcome to try it though :)
For auto PDF to Image conversion you will need the "convert" command on your server with "exec" permission.

Installation

Installing Hercules should be relatively easy. Once you have a running PHPDevShell, it should be very easy following these steps;

  • Extract your copy of HerculesPostman, it should be in this named format too.
  • Copy the whole folder over to your PHPDevShell installation under folder name "plugins".
  • Open PHPDevShell and go to System Management->System Admin->Plugins Admin.
  • Look for the HerculesPostman Plugin and click on the Install (You will see a list of green text being executed).
  • Hercules should now be installed, to fine tune the mailing process performance, go to System Management->System Admin->Config Manager. In the search box, type in "hercules". You will now see a list of settings related to Hercules. Increasing numbers will make Hercules send out more mail per batch, but will be much harder on the system if increased significantly.
  • To make Hercules run at its optimal best, make sure you setup PHPDevShells cron as per the install instructions and then find your way to System Management->Cronjob Admin->Cronjob Manager and edit "Mail Process":
    • Execute Cronjob: Repeat Indefinitely
    • Minute(s): 3
    • Log Cronjob Action: Yes
  • Hit save, your cronjob is now set and you are ready to start sending mail.

Usage

Hercules is very easy to use and there are tooltips for all fields. To get yourself started we included 3 basic templates and a default structure id a new mail is being created.

Mail Manager

The first screen called Mail Manager is where you will create mail. This easy to use screen has but 2 mandatory fields, the subject and to what groups you will send the mail too.

Hercules uses users from the PHPDevShell user table to send mail too. They can be either imported or created. They must also belong to some group before they can be send to users.
After entering a subject and choosing your groups, save draft and more tabs will become available.

Under the tab Mail Information, you can also set other information related to the mail. The Body tab contains the mails template or body. Once you obtained a standard body that you are happy with this will seldom need to be touched. Images uploaded under the Body tab is only meant to style the body with. You have a set of tags to use inside your mail to generate mail uniquely for each user.

You can use the body editor to create or use any email template you wish by copying in the source code and uploading images and setting the correct tags.

  • Add [TITLE] tag to HTML body for title name value.
  • Add [LANG] tag to HTML body for system lang value.
  • Add [CHARSET] tag to HTML body for system charset value.
  • Add [URL] tag to HTML body for servers absolute url.
  • Add [DATE] tag to HTML body for sent time date.
  • Add [IMAGES] tag to HTML body for auto image integration.
  • Add [BROWSER]Text[/BROWSER] tag to HTML body for auto view In browser link.
  • Add [UNSUBSCRIBE]Text[/UNSUBSCRIBE] tag to HTML body for auto unsubscribe link.

To focus on some tags like the [IMAGES]. You will be happy to know that when inserting a [IMAGES] tag gives you the capability to simply upload images without further intervention. If you click on the Inline Images tab, selecting the Inline options and uploading an image. These images will now automatically replace the [IMAGES] tag.

Attachments tab is used to physically attach files to mail. Be careful though this and embedded images is very bandwidth intensive and could kill your bandwidth. It differs from inline images as users can download images only if they wish making mail very small in size.

Mail Manager Submit Functions

Save Draft

Will save the mail as it is (edit from Mail Queue), nothing will be send out. When editing a mail that was already queued to be send out, saving it as draft again cancels the sending action and deletes the queue for this mail.

Send Preview

Will save mail as draft and send out a preview to yourself and whomever is listed in the Preview CC field. This is to make your your mail is ok for final sending.

Auto Deliver

This important button is the final action for delivering your mail. This will tell the cron system it is ready to be taken care of. When this action is executed the whole cron process will start and your mail will be delivered in chunks.

Send Manually

Not often recommended, however, if you only have a few hundred mails to send, you can use this for immediate delivery. Though... when you have thousands, use the Auto delivery system instead.

The other options is self explanatory.

Mail Queue

The Mail Queue is current status of mail and how the processing is going. Unser the status column you will not three numbers. They represent the status of the mail being send. The first number is successful mail being send, the second is mail being sent with errors and the third is how many mail there is too send.

Hercules uses a rather complex process to make sure all mail is queued and send, this is done in cycles, the first few cycles is only to queue the mail. So be patient if your mail is not delivered immediately.

Reuse: Hercules has a nifty reuse button that allows you to resend mail based on other mails or templates.
Mail Processing

The Mail Processing screen is current mail that is queued for delivery soon. You can clear this table to stop processing immediately.

heading_Screenshot-1.png heading_Screenshot-3.png

Support

Pagination and Filtering

PHPDevShell offers a plugin (Pagination) that empowers you to easily create database listings that enable your users to:

  • Search for records
  • Filter by column
  • Page through records

To make it as easy as possible we will use the ExamplePlugin as reference for this tutorial, you will find it easier to follow if you open the ExamplePlugin.

It all happens in the Model

Like you would expect the hardcore queries happens in the model, and this is where we will start with this guide. Lets open

plugins/ExamplePlugins/models/list-example.query.php

To begin the pagination process one needs to override the invoke method of your model, like this:

class ExamplePlugin_readExampleQuery extends PHPDS_query
{
	protected $sql = "
		SELECT
			id, example_name, example_note, alias
		FROM
			_db_ExamplePlugin_example
    ";

	/**
	 * Initiate query invoke command.
	 * @param int
	 * @return array
	 */
	public function invoke($parameters)
	{
		// Pagination will happen here...
	}
}

The next step is to call the pagination plugin, this is simply done by:

You don't have to specify the column/field names in the SQL SELECT statement, you can just use:
SELECT * FROM _db_my_table

However, if you do specify the columns make sure to specify all the columns that you will specify in $pagination->columns;

	public function invoke($parameters)
	{
		// Initiate pagination plugin.
		$pagination = $this->factory('pagination');
	}

This will give us access to all the needed features of the pagination class.

Selecting the Columns

The columns assignment serves many purposes, writing code only once you will;

  • Set what columns to search in when using the search box.
  • Creates the html for the heading tags.
  • Creates the ordering and filtering icons for the tags.

You will be assigning values to the pagination object array called

$pagination->columns;
Array Key

The key of the array is the column name inside the tag of the auto created header. It can be left empty to include the specified database column name in the value field without showing it in the header.

Value

The value part is the database column/field name. This will be included in the selections for the search filter. This value can also be left empty, and if a key is specified, the header tag will still be created for alternative columns.

Assigning the columns for pagination and filtering:

		// Assign columns and th headings.
		$pagination->columns = array(
			_('Example ID') => 'id',
			_('Example Name') => 'example_name',
			_('Example Notes') => 'example_note',
			_('Example Alias') => 'alias',
			_('Edit Example') => '',
			_('Delete Example') => '');

Now that we have set our column we are ready to call our query above. When no pagination is required, we would normally just call the parent invoke method

$example_array = parent::invoke();

however, since we want to call the paginations plugins query method (so we can manipulate the sql), we do this instead:

$get_results = $pagination->query($this->sql);

This now contains the array of data required to complete the columns results. Before we continue we will look at what else is returned to us after we used the query call.

Just like with invokeQuery you can also pass values to replace %u, %s etc with. This is passed the same way as invokeQuery like this:

$get_results = $pagination->query($this->sql, $var1, $var2, $var3, etc...);
Note that we will assign all the different parts of the returned query, to an array called $RESULTS. This allows us to just return a single array ($RESULTS) at the end of the query, and then divide the different sections up from within our controller.
Search Form

Creating your search for is easy, the nice thing about the search form is that is remembers what you searched per user. Draw the search form calling a single method:

$RESULTS['searchForm'] = $pagination->searchForm();
Column th Headings

The returned and drawn up html th heading is gathered by using method:

$RESULTS['th'] = $pagination->th();
Pagination Links

We would also need the links to allow the navigation through the different pages. We get this using this method:

$RESULTS['pagination'] = $pagination->navPages();

Other properties...

There are a few methods that can be override, however, the provided properties will mostly be enough to get around.

Condition

For pagination, the system adds its own condition to the sql, $pagination->condition is to manually specify what to add before its own condition starts. But as I mentioned this will be added automatically by the system and can be left alone.

PHPDevShell will automatically determine whether AND or WHERE should be used. You can use

$pagination->condition = 'AND'

if you manually want to determine this. However, if I used WHERE in query above, AND would have been used automatically.

Extra SQL

You can add extra sql at the end of the query using

$pagination->extraCond = 'more sql...'

This is simply a condition that will be added after all SQL, it could be usefull in some cases, however, it will mostly not be useful. Useful for;

$pagination->extraCond = 'GROUP BY student_name';
Limiting paging results

PHPDevShell will use the page result limit as set in the general settings ui, however, it can be override by using

$pagination->limitCount = 50;

Note that limitCount needs to be before all the other calls in order to work properly, like this:

$pagination->limitCount = 50;
$get_results = $pagination->query($this->sql);
$RESULTS['th'] = $pagination->th();
$RESULTS['searchForm'] = $pagination->searchForm();
$RESULTS['pagination'] = $pagination->navPages();

Looping and collecting results

The last part in the model is to collect the results in our results array, add the extra edit and delete colums and return it to our controller.

Looping will be done normally and reassigned to our $RESULTS array to return it to the controller.

		// Simply loop through the results and build values.
		foreach ($get_results as $e) {
			$id = $e['id'];
			$example_name = $e['example_name'];
			$example_note = $e['example_note'];
			$alias = $e['alias'];

			$RESULTS['list'][] = array(
				'id' => $id,
				'example_name' => $example_name,
				'example_note' => $example_note,
				'alias' => $alias,
				'edit_example' => "<a href=\"{$page_edit}{$id}\" class=\"button\">{$this->template->icon('key--pencil', _('Edit Example'))}</a>",
				'delete_example' => "<a href=\"{$page_delete}{$id}\" {$this->core->confirmLink(sprintf(_('Are you sure you want to DELETE : %s'), $example_name))} class=\"button\">{$this->template->icon('key--minus', _('Delete Example'))}</a>"
			);
		}
		if (! empty($RESULTS['list'])) {
			return $RESULTS;
		} else {
			$RESULTS['list'] = array();
			return $RESULTS;
		}

We now have the pagination results and all the html we need to build a fully paginated page.

Our Controller

The controller part of things are just as easy, we will simply invoke the query and assign our different parts to different variables for the view to handle. You will see that the different parts of $RESULTS will be re split here;

		// Lets pass the array to the view so he can loop and output the results.
		$RESULTS = $this->db->invokeQuery('ExamplePlugin_readExampleQuery');

		// Load views plugin.
		$view = $this->factory('views');

		// Assign different parts of our pagination to different variables.
		$view->set('pagination', $RESULTS['pagination']);
		$view->set('searchForm', $RESULTS['searchForm']);
		$view->set('th', $RESULTS['th']);
		$view->set('RESULTS', $RESULTS['list']);

		// Output to views.
		$view->show();

This is basically all we need from the controller.

View

The last remaining step is to place the passed variables in the view for processing. These variables contains standard strings and arrays. So it does not matter what type of view you have, you can just loop through it using appropriate methods. We now simply fill in the variables where we want them.

{$searchForm}
<table class="floatHeader">
    <thead>
		<tr>
			{$th}
		</tr>
    </thead>
    <tbody>
        {foreach item=groups from=$RESULTS}
        {strip}
        <tr>
            <td>
                {$groups.id}
            </td>
            <td>
                {$groups.example_name}
            </td>
            <td>
                {$groups.example_note}
            </td>
            <td>
                {$groups.alias}
            </td>
            <td>
                {$groups.edit_example}
            </td>
            <td>
                {$groups.delete_example}
            </td>
        </tr>
        {/strip}
        {foreachelse}
        <tr>
           <td class="no_results" colspan="6">
            {_e('Your filter request does not match any data.')}
           </td>
        </tr>
        {/foreach}
    </tbody>
	<tfoot>
        <tr>
            <td colspan="6">
                {$pagination}
            </td>
        </tr>
	</tfoot>
</table>

By default all columns in the above table will be spaced equally. I nice way to set specific column widths is to use the CSS3 nth-child() selector. Our View above contains 6 columns, to give each column a unique width add the following inline style to your template:
{$searchForm}
<style type="text/css">
    th:nth-child(1) { width: 5%; }
    th:nth-child(2) { width: 15%; }
    th:nth-child(3) { width: 65%; }
    th:nth-child(4) { width: 5%; }
    th:nth-child(5) { width: 5%; }
    th:nth-child(6) { width: 5%; }
</style>

<table class="floatHeader">
.
.
.

This should now work, make sure to check the ExamplePlugin to view full mvc for the code to make sense.

Unfinished development topics

Featuring more advanced topics when developing in PHPDevShell.

The Tagging mechanism

The Tagging mechanism is a simple and widely used way of attaching data to things. Its purpose is to allow the developer to have a single object, such as a page, and use it differently depending on a piece of data.

Let's look at an example.

I have a page which gives statistical graphics for a given country. Each country is managed by a given user, and other users should not have access to other countries. The method is to create a one page which displays the graphics depending on a given country code. Then to create page links pointing to this generic page, each link being "tagged" with a country code. Now each country-specific page can be given a specific set of rights.

Tags can be attached to pages (i.e. menu items), users, groups and roles, or even arbitrary entities.

You usually set tags through the GUI, however you can use them completely from code.

Using tags is very simple ; for example, to fetch the country code in our example:

$countryCode = $this->tagger->tagHere('country_code');

switch ($countryCode) {
	case 'us': ...
}

Using this code in the code of the "generic page", just use the GUI to add a tag "country_code" with the value "us" to the link for the USA (and other tag values for other country-specific links).

The Tagger can attach tags to anything with the generic call:

function tag($object, $name, $target, $value = null);

where:

  • object is the kind of entity of the target : it's an explicit text string, such as "user", "menu"
  • target is the specific entity to attach to: it's also a string containing a reference to the entity, usually it's unique ID in the database
  • name is the label of the tag, the "key" to the value : it's a string used to match the tag label given in the GUI
  • value is the actual value of the tag for the given entity
As you can see, the entity is uniquely identified by the couple (object, target), since for example a menu and a user can have the same numerical ID.

Note that this function is both a getter (when no value is given) and a setter (when a value is given). Example:

$this->tagger->tag('user', 54215, 'passed', 'yes);
print $this->tagger->tag('user', 54215, 'passed'); // outputs "yes"

Very simple. To be even easier to use, several shortcuts are provided:

function tagUser($name, $target, $value = null)
  • just give the user ID
function tagMe($name, $value = null)
  • tag the current user
function tagMenu($name, $target, $value = null)
  • just give the menu ID
function tagHere($name, $value = null)
  • tag the current menu
function tagRole($name, $target, $value = null)
  • just give the role ID
function tagGroup($name, $target, $value = null)
  • just give the group ID

Utility functions

The framework offers you a bunch of utility functions which are not directly connected to the core classes.

They are groups into several themes: url creation, string related, internationalization, misc.

URL creation

In html url are string used inside tags, often using parameters. These functions help you create such things, for example based on a PHP arrays.

PU_BuildGETArray

function PU_BuildGETArray(array $myGET, $includeInGet = null, $excludeFromGet = null)
Example call Result
PU_BuildGETArray(array('a'=>1,'b'=>2), array('c'=>3));
	Array
	(
   		[a] => 1
		[b] => 2
    		[c] => 3
	)
PU_BuildGETArray(array('a'=>1,'b'=>2), array('c'=>3), array('b'));
	Array
	(
   		[a] => 1
    		[c] => 3
	)

PU_BuildGETString

This function takes the $myGET array, removes anything which has a key in $excludeFromGet, and adds whatever is in $includeInGet. The return value is an array.

function PU_BuildGETString(array $myGET, $glue = '&');
Example call Result
function PU_BuildGETString(array('a' => 1, 'b' => 2);
	?a=1&b=2

This function generates a string from the given array to be used to build the GET part of a string URL. The $glue parameter allows to used something else than an ampersand between parameters.

PU_BuildGET

This function is a compound of the previous, using the meta array $_GET.

function PU_BuildGET($includeInGet = null, $excludeFromGet = null, $glue = '&')
Example call Result
$_GET = array('a' => 0, 'c' => 3, 'd' => 4);
function PU_BuildGET(array('a' => 1, 'b' => 2), 'c');
	?a=1&b=2&d=4

PU_BuildAttrString

function PU_BuildAttrString(array $attributes = null, $glue = '')

Similar to PU_BuildGETString(), this function creates a xml-style attribute string from the given array.

function PU_buildParsedURL($p)

Above is just an example of what is available, for more see : http://doc.phpdevshell.org/d6/d57/_p_h_p_d_s__utils_8inc_8php.html

Working Cycle

This page will describe the complete cycle a request being processed from entering the web server to being sent back to the browser.

Overview

The following flow chart describes the whole process:

Writing and Reading from Cache System

After setting up a cache system in your configuration, whether its Memcached, APC or File Cache you can read and write from your application using PHPDevShell's universal cache functions and it is very easy to use.

Writing data to cache is very similar to working with sessions, you can read from it at any time and you don't even have to be on the same page. All you need to know is the key you stored it with originally. Cache is stored on a per user basis, meaning every user accessing PHPDevShell from his/her computer will have his own copy of the cache on the server.

A quick demonstration on how to write data to the cache:

	// Get required menu data.
	if ($db->cacheEmpty('mykey123')) {
		$data = array('some', 'data', 'to', 'write'); // Could just be a string too!!
		$db->cacheWrite('mykey123', $data);
		$db->cacheWrite('othermykey123', 'My name is Dally!');
	} else {
		$data = $db->cacheRead('mykey123');
		$name = $db->cacheRead('othermykey123');
	}
	print_r($data);
	echo $name;

That is all there is to it. We have written data to the cache and in this case we even checked whether the cache already existed or has expired. Obviously this example won't be very useful, but you would use it on large static database results or where expensive CPU cycles can be avoided. Using cache where it counts can speed up your application dramatically.

Themes, Skins, Templates and Modules

article_HTML5-wow_610x351.png

PHPDevShell offers a very unique and powerful way of displaying content to the end user. You are truly limitless in regards with displaying content using whatever laying, technology or style prefer. Latest technologies like HTML5 integrates seamlessly. You can even use CSS frameworks without any hassles at all. You will learn to adore the powerful template engine of PHPDevShell.

PHPDevShell divides technology up into Themes, Skins, Templates, Modules and Views which we will explain and cover next.

Themes, Skins, Modules and Templates

Themes are used to determine the main structure and layout of your page. Themes can be shared or even assigned to a single node/menu. So that means every controller can have its own theme. Your theme is mostly used to integrate a CSS framework like boilerplate or Jquery UI.

Themes are located under themes/ and contains a config file named themes/themename/config/theme.config.xml. The theme config, although simple makes it possible to install your theme from the UI under Theme Management. After installation themes can be assigned to menus.

PHPDevShell is designed with pure HTML, no obtrusive everywhere in HTML to remember. Just pure HTML will make your view look exactly the same as the rest of PHPDevShell.

a Theme configuration file will look typically like this:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>

	
		Cloud
	
	
		3.0.0-RC-1
	
	The new PHPDevShell V3 template aiming to be a clean and fast template.
	Jason Schoeman
	Jason Schoeman
	titan@phpdevshell.org
	http://www.phpdevshell.org
	4 August 2010
	Copyright 2007 PHPDevShell.org All rights reserved.
	http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html GNU/LGPL

For the duration of this documentation we will refer to the name "cloud" which is the default theme for PHPDevShell administrator interface. We will look at what it consists of and break it down into smaller parts for a better understanding of how we structure a page.

The main structure of your theme will sit under themes/cloud/theme.php, this file contains from the header to the footer and resembles the main layout of your website. Inside this structure we call smaller parts named modules to fill in the dynamic parts of your page. You can choose what modules you would like to use in your page. The module html can be found under themes/cloud/modules.php

It should be clear to you that inside theme.php is also where you will call your CSS/Skin from. This will help you add a look and feel to the structure of your page. For the cloud theme, we use Jquery UI as the skin of the theme.

To call html snippet modules we use methods from the Template system. The template class holds a collection of useful methods to make your theming easier. So all you would do is call these methods from within your theme.php, and example from cloud would look like this:

<!DOCTYPE HTML>
<html lang="<?php $template->outputLanguage() ?>">
	<head>
		<title><?php $template->outputTitle() ?>
		<meta charset=<?php $template->outputCharset() ?>>
		<meta http-equiv="X-UA-Compatible" content="IE=8" />
		<meta name="keywords" content="<?php $template->outputMetaKeywords() ?>" />
		<meta name="description" content="<?php $template->outputMetaDescription() ?>" />
		<?php require_once 'themes/cloud/include.php'; ?>
		<!-- Custom Head Added -->
		<?php $template->outputHead(); ?>
	</head>
	<!-- PHPDevShell Main Body -->
	<body id="container" class="ui-widget ui-widget-content">
		<header>
			<!-- LOGO & LOGIN DETAILS -->
			
			
<?php $template->outputLoginLink() ?> ,  <?php $template->outputRole() ?> ,  <?php $template->outputGroup() ?>
<?php $template->outputTime() ?>
</header> <nav> <!-- BREADCRUMB MENU AREA -->
<?php $template->outputScriptIcon() ?>
    <?php $template->outputBreadcrumbs() ?>
</nav>
<!-- OUTPUT CONTROLLER AREA --> <?php $template->outputController() ?>
<!-- FOOTER AREA --> <footer class="ui-state-disabled">
<?php $template->outputTextLogo() ?>
<?php $template->outputFooter() ?>
</footer> </body> </html>

So this means, that I can call a single module using the templates methods. So as an experiment we can for instance do out layout with just the view of a controller. So a theme.php like this would work just fine.

<html>
  <head>
    <title>My Own Theme</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <link rel="stylesheet" href="<?php echo $url ?>/themes/myowntheme/css/theme.css" type="text/css" media="screen, projection" />
  </head>
  <body>
    <?php $template->outputController() ?>
  </body>
</html>
Above example will contain no menus, or anything else. It will simply load the view html that the controller puts out. This is to show how template modules are loaded and that views can determine layout. This makes the layout possibilities endless.

themes.jpg

So lets just look at the different parts again;

Themes

Themes is the structure of your whole page.

Skins

Skins is your javascript and CSS for your theme to style it.

Template

Templates is the class that handles your theme and also helps you output dynamic chunks of modules to your theme.

Modules

Modules is smalled chunks of html that is handled by templates.

Views

Views are your html that is output by your controller.

In conclusion

So in conclusion, you will realize how easy it is to simply download your favorite theme/ccs framework to be used in PHPDevShell. Or designing a theme in Photoshop, having it sliced up and using it in PHPDevShell is but a breeze. Your theme can be designed to function the way you want it to function and work.

Building a Content Website

Before reading all the details about writing code for PHPDevShell, you might wonder how you could build a website with it. Despite what you may feel at first glance, most any kind of website can easily be built with PHPDevShell; in fact, many websites around the world are, and most users don't realize that. The layout can be very diverse, like for an example website see Uhambi

After a fresh install, a PHPDevShell site looks like a "modular console": a clean interface composed of icons to manage various administration tasks (user administration, access control, and many more). All these functionalities are bundled into plugins, and you might think adding your own will only add icons to the administrative interface. But you can do much more than that, based on two really simple concepts:

  • any page can be "the home page"
  • every page can have its own look

So, let's say you write a plugin which has a script just saying "hello world"; all you need to do is to specify, for this page, a look that DOESN'T show the menus, and set it as your site home page. When a user comes to your site, (s)he will only see "hello world". Of course, a real web page is much more complex, but now you can use PHPDevShell advanced features like database access, security settings, templating, localization... to make your work easier.

And don't forget you will need to keep a way to access the administrative interface, but it's up to you to decide how you want to do it.

PHPDevShell spits out clean HTML, this means you can do with it whatever you like. Vertical menus on a highly sociable website is very easy! You might wonder what your limitations in design is? The answer is: absolutely no limitations, the sky is the limit.

Getting started

a Good starting point would be to read the Theme documentation and understanding how a theme works. We will not be explaining everything here. All we will get you started with is actually getting site up, looking different then PHPDevShells default administration page.

To build any kind of website one mostly needs a surrounding theme with a core structure. PHPDevShell has a very intuitive system where it handles the main theme, styling and then the page/controller itself differently with different layout possibilities. We will be focusing on the main theme.

All main structural themes are stored in themes/ where the default administration theme for PHPDevShell is cloud. We will be creating our own theme in the themes/ directory. For this example I am going to call it mySite (call it whatever you like). So first thing you do is create a folder under themes/mySite, you also need to tell PHPDevShell that it is there, to do so, create another folder inside mySite and call it config. Now as a template to the config file copy the cloud/config/theme.config.xml to themes/mySite/config/theme.config.xml. We can now continue to edit the file with our own detail:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<install type="template">
	<name>
		mySite
	</name>
	<version>
		1.0.0
	</version>
	<description>My new site.</description>
	<founder>Jason Schoeman</founder>
	<author>Jason Schoeman</author>
	<email>titan@phpdevshell.org</email>
	<homepage>http://www.phpdevshell.org</homepage>
	<date>7 June 2011</date>
	<copyright>Copyright 2007 PHPDevShell.org All rights reserved.</copyright>
	<licence>http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html GNU/LGPL</licence>
</install>

Now that we are telling PHPDevShell that there is a new theme, we can continue creating the main structure file. The main structure is always called themes/mySite/theme.php. This file contains nothing special besides a few methods that will be included later. For now though create a new file called themes/mySite/theme.php lets add some content to it.

    <!doctype html>  
    <html lang="en">  
    <head>  
      <meta charset="utf-8">  
      <title>My New Site</title>  
      <meta name="description" content="The HTML5 Herald">  
      <meta name="author" content="Example Site">
    </head>  
    <body>
	<h1>My new Site</h1>
	<p>This is my first site on PHPDevShell.</p>
    </body>  
    </html>  
Ok this is a basic HTML5 starting point, now you can obviously add your jquery, css and everything in here like you would normally do, it is really up to you what you include with it. You can even split the page up, but I personally don't like having a header file, footer etc. split up.

Install your theme

Now that we created a new theme, we would want to install it. This is simply done by launching PHPDevShell, System Management->System Admin->Themes Admin locate your theme and hit install.

The theme is now installed, and we can link a page to it. PHPDevShell works very intuitively, to make it as flexible as possible, you assign a menu item to a theme. So each and every page you view can be assigned to a different theme. We now want to create a new page, we can call it home-page for this tutorial.

Creating page content

You can if you like assign this theme to any existing menu, but to make out example as thorough as possible we are going to create our own page.

For the next part we are going to kindly ask you to follow this getting started tutorial, this will guide you through the process of creating a page. The only difference is, for the theme, assign your own newly created theme from the dropdown.

You will be happy to now, that if you access your newly created page now, your new theme will appear. It wont show anything in it yet as everything was done with static content.

Adding the content of your page to your theme

The next step will allow us to add everything from the page item into the theme dynamically. The theme has a few methods at his disposal. For the most prominent ones read this page. For now lets focus our energy on the method that calls the content from the page you created in the previous section. Namely $template->outputController(), this method allows you to display the content from you page you created that gets echo'd inside your theme.

Lets change theme.php and add this method. PS: Look at the other methods (like menus) to include in your theme.

    <!doctype html>  
    <html lang="en">  
    <head>  
      <meta charset="utf-8">  
      <title><?php $template->outputTitle() ?></title>  
      <meta name="description" content="The HTML5 Herald">  
      <meta name="author" content="Example Site">
    </head>  
    <body>
	<h1>My new Site</h1>
	<p>This is my first site on PHPDevShell.</p>
	<div id="bg">
		<?php $template->outputController() ?>
	</div>
    </body>  
    </html>  
Remember, you can assign as many menu pages to one theme as you like. View the menu management and assign your menus easily from there. You can also create a more defined structure in each page you create that "outputController" will spit out.

Now you have completed the three crucial stages of creating your own custom page with its own theme, namely; Creating a theme, creating a page, assigning your page to your theme. If you did everything according to this tutorial, you should now see something like this:

Screenshot-4_0.png

Now this might be your very first home page. So you want to this page to open when users browses to your site. PHPDevShell offers a switch to redirect logged-out users to a page you prefer for "home" and a different or the same page for logged-in users as home. To do this you need to open the Admin UI again and browse to System Management->System Admin->System Settings. Open the themes tab and change Frontpage Link (When logged out/out) to your page you created in "Creating page content". Now this will be your default home page when users visits your site.

Screenshot-5.png

In conclusion

As you might see, using your imagination you can truly create completely different and proper layouts/styles etc for each of your pages. You might want to learn how to create pages properly by using MVC, however, this is truly up to you. You might be interested in the following subject to educate yourself in the next phase of your website development;

Maintaining (or customizing) Theme Modules

The modules file located in themes as mod.php is a simple system to strip out all the html inside the core of PHPDevShell allowing you to change any aspect of hard coded HTML.

At some stage you will probably need to customize some module methods. Instead of maintaining all of them you only need to maintain the ones you you customized. You can do this by overriding the original one.

Your custom theme will need to have its own mods.php file, inside this file you will add the override code of the modules you wish to customize. There is a default and build in auto loader. It will first check if the class name exists in the form of your theme name. Lets call this theme for example myCustomTheme.

You will now continue to override it into something like this:

include_once 'themes/cloud/mods.php';

class myCustomTheme extends themeMods
{
	public function search($action, $value, $class, $validate)
	{
		// Customized search form html...
	}
}

This should be simple enough.

If you're writing this as a part of a theme, just name the file "mods.php" and it will be used. If however you want to change the display of current theme but not alter the theme itself, you can manually declare it. There are two possibilities:

either declaratically, from your config xml file, add the following to the <classes> clause:

	

OR programmatically, from your code (or php config file), add the following line:

$this->classes->registerClass('myCustomTheme', 'themeMods', 'myPlugin');

and put your class in the file myCustomTheme.class.php in the includes folder of your plugin. It can now be called like this:

// include_once 'themes/cloud/mods.php'; // see not below

class myCustomTheme extends myCustomTheme
{
	public function search($action, $value, $class, $validate)
	{
		// Customized search form html...
	}
}

Note: don't include the original mods.php, because it will be already loaded ; furthermore, if the page uses another theme, there will be a name conflict.

Common mods to change

Here are some of the mods you may want to change:

  • the logo part:
    public function logo($url, $src, $alt, $title)
  • the login form:
    public function loginForm($action, $username_label, $password_label, $redirect_page, $lost_password, $lost_password_text, $not_registered_yet, $not_registered_yet_text, $remember, $security, $login_label, $user_name)
  • the login top-right link:
    public function loggedInInfo($href, $text)
    public function logInInfo($href, $inoutpage)

i18n Language Localization Translation

PHPDevShell uses the brilliant gettext (po & mo) translation system. This system performs extremely well and is used in many open source systems. PHPDevShell has the capability to completely translate the core system with 3rd party plugins to a different language, this without influencing how the developer should develop.

We have had very little support from the community willing to translate PHPDevShell. Thus the only current languages available are English and French. The whole translation system is already in place though, if you are willing to translate PHPDevshell to another language please don't hesitate, translation is easy with our new launchpad implementation! Please see: https://translations.launchpad.net/phpdevshell

PHPDevShell's developers chose to use the GNU gettext localization framework to provide localization infrastructure to PHPDevShell. gettext is a mature, widely used framework for modular translation of software, and is the de facto standard for localization in the open source/free software realm. gettext uses message-level translation — that is, every "message" displayed to users is translated individually, whether it be a paragraph or a single word. In PHPDevShell, such "messages" are generated, translated, and used by the PHPDevShell PHP files via a PHP functions. __() and a method $this->core->__() is used inside heredoc tags or elsewhere where __() will not work.

This means anything you write inside __('Hello World') or $this->core->__('FooBar') can be translated to any other language or string. The whole of PHP core is done with these language functions meaning the whole PHPDevShell can be translated. You can develop your plugins in the same way, this will allow your plugin to be available in many languages you choose.

The great thing about this translation technology is that even if a string is missing in the translation it will still translate those that are available.

Step 1: Translate your language if not available

If your language is not already translated, you will have to do so yourself, this is very easy though.

Step 2: Download and rename translation files

The next process will show you how to download and what to name the downloaded translation packages.

  • Click on the link https://translations.launchpad.net/phpdevshell/trunk
  • Click on the language of one of the three translation packages.
  • Click on download selecting .mo format and repeat the process to download core.lang, plugin.lang, hercules-messages.
  • Rename the files from => to;
    • ??_LC_MESSAGES_plugin.lang.mo => PHPDevShell.mo
    • ??_LC_MESSAGES_core.lang.mo => core.lang.mo
    • ??_LC_MESSAGES_exampleplugin.mo => ExamplePlugin.mo
.mo files are compiled language files, each language has its own. This compiled code makes it incredibly fast to find a translation for a string. This is a standard translation system for many open source products.

Step 3: Prepare and set correct language options in PHPDevShell

You are now ready to make the required settings in PHPDevShell to enable the translation.

  • Enable FirePHP to check to find where templates mo files are being looked for by PHPDevShell.
  • Under System Management->System Admin->Server Settings enable the translated language. Locate "User Registration Settings" under "User Registration Settings" there are two multiple selection boxes called Languages Available and Regions Available. Select the translated language and region and click save, this will then be available for your users to choose when they register or from their user preferences else falling back to the default language selected seeing that you can also make your translated language your default language in System Settings.
  • Select your language and region from your "User Preferences" page for it to activate the correct language lookup.
  • You will note the language debug system now display the locations of .mo files it looks for, copy the downloaded and renamed .mo files to its correct directories.

I have switched my language to Afrikaans in South Africa, a Typical example of where the system will look for the required .mo files are;

plugins/HerculesPostman/language/af_ZA/LC_MESSAGES/HerculesPostman.mo
plugins/PHPDevShell/language/af_ZA/LC_MESSAGES/PHPDevShell.mo
language/af_ZA/LC_MESSAGES/core.lang.mo
Note the "af_ZA" folder that is the language_REGION for your selected language. You can also find this code in your "User Preferences" under "Locale Language, Region and Charset Information", this will form part of the language directory.

Translating Date and Time.

At the moment it is not possible to translate date and time, this is a limitation with the Datetime class of PHP. If this is a problem, change the date format to use numbers only from the General Settings ui inside PHPDevShell.

Does it not work? Remember these

The gettext framework is sometimes very fragile, there are a few thing to remember when translating, always first try the default settings then;

Is your language available on the server PHPDevShell is running on? In popular distros like Ubuntu, you can install it like this: (af is the code of the language)

$ sudo apt-get install language-pack-af

$ sudo locale-gen
  • The locale must be available on the server it is hosted on for it to work.
  • You might need to adjust your locale format for your server in PHPDevShells settings under Combined Locale Language, Region and Charset Format.
  • You cannot use a compiled Windows mo file on Linux, if you are going to use it on a Linux server compile it using linux. (Can someone confirm this?)
  • Sometimes when you find things that just wont translate properly switching back to default randomly, try restarting Apache. Sounds crazy but it works.

If you are having problems, please take the time and read through these comments http://www.php.net/manual/en/ref.gettext.php, PHPDevShell does its best keeping it exactly like PHP recommends it, but sometimes servers configuration just differs too much.

Translating your own plugin

PHPDevShell extends its translation system making it available for third party plugins. Its very easy to use, and making your plugin translatable only required a simple function.

Functions to use making translation possible

The reason to use specific functions serves two purposes. The one is to enable the system to return the alternative translated string required. The other reason is to enable xgettext (parsing extractor) to extract the original string to a translation template called a POT file. After the POT file gets extracted it is ready to be translated through a third party application like PoEdit or Launchpad Translation service. After a POT template is translated it gets saved as a PO file and gets compiled as an MO file.

Gettext uses textdomains, a textdomain group different plugin translations together. Unfortunately, gettext can only use one textdomain at a time. This can be overcome but it does cause a little extra work, the reason being, we already have the core textdomain and the plugin PHPDevShell textdomain running. To overcome this textdomain problem, sometimes when not translating a controllers strings you might need to pass the textdomain through with the string to tell gettext where to look for the translated string. The textdomain name is normally the same name as the plugin.

The following functions can be used in your code to enable translation. Note that they all perform exactly the same purpose.

// For general translation.
print __('This string can be translated now.');

// Inside a heredoc
print "

{$this->core->__('Translate me')}

"; // Shorthand alias - (this is a direct php gettext function) - Cant pass text domain. print _('You can use this too.') // When a textdomain needs to be passsed. print __('Translate me','ExamplePlugin'); print $this->core->__('Translate me','ExamplePlugin'); // What about inside views or anywhere where an echo is needed? __e('No echo needed'); __e('No echo needed', 'ExamplePlugin'); _e('No echo needed'); // This is reserved for the core and always uses textdomain core: print ___('Only for used inside PHPDevShell core.');

This is basically all you need to encapsulate strings in your code with to make it possible for gettext to extract and to make them translatable.

When to pass textdomain

When translating a plugins controller or model, the textdomain will be automatically set when your plugin is being used. However, if your plugin has public available classes, then the active textdomain will be set to whatever plugin is accessing your public class. To tell the gettext where to look for the translated strings you need to pass the textdomain of your plugin. This is very simple to do. The text domain would be the exact name of your plugin/folder your plugin resides in.

// Passing text domain.
print __('This string can be translated now.', 'PluginName');
// So for ExamplePlugin it would be:
print __('This string can be translated now.', 'ExamplePlugin');
You only need to pass the text domain for public sharable classes.

Extracting your plugin language strings to a pot file

PHPDevShell ships with a utility to execute and extract, this executable .sh file can be located inside other/language.sh. Unfortunately, this is only available to linux and mac users. Simply execute the command, enter plugin name, and all language strings will be extracted and saved into your plugins language folder. Make sure you have xgettext installed on your machine.

sh ./other/language.sh

Windows users can download gettext here http://ftp.gnu.org/gnu/gettext/, after installation you will need to run your files manually to extract the strings. You will need to run it twice for php and python as the standard php type seems to skip heredoc, if you don't have heredoc present then there is no need to extract with the python method.

For Windows Users Only!

		xgettext.exe -o c:\phpdevshell\plugins\ExamplePlugin\language\ExamplePlugin.pot --from-code=utf-8 --no-wrap --language=PHP --keyword=_ --keyword=__ -j -f c:\phpdevshell\plugins\ExamplePlugin\example.php
		xgettext.exe -o c:\phpdevshell\plugins\ExamplePlugin\language\ExamplePlugin.pot --from-code=utf-8 --no-wrap --language=Python --keyword=_i --keyword=__i --keyword=_e --keyword=__e --keyword=_ --keyword=__ -j -f c:\phpdevshell\plugins\ExamplePlugin\example.php
Note, You might have noticed that I use Python under the --language selector, there is a good reason for this, if I choose php it will not look under heredoc tags leaving some string un-extracted. This is kind of a limitation, but choosing Python handles this correctly.

Translating the POT file to the new language and saving as compiled MO

You should now see a plain text .pot file in your language directory. This file contains a typical original string and below it space for a translated string. It looks like this:

#: plugins/ExamplePlugin/controllers/example/upload-example.php:66
#, php-format
msgid "File field 3 %s was uploaded"
msgstr ""

If your POT file is not empty, you are now ready to start the translation process.

This process is rather simple if you too use the https://launchpad.net/ system like PHPDevShell does, however if this is a once-off translation effort you can use any other of the following tools to load the .po file and do the translation.

poEdit: I recommend this application, an open source program for Windows, Mac OS X and UNIX/Linux which provides an easy-to-use GUI for editing PO files and generate MO files. I strongly recommend this tool, it can be downloaded here; http://www.poedit.net/

KBabel: Another open source PO editing program for the KDE window manager on Linux.
GNU Gettext: The official Gettext tools package contains command-line tools for creating POTs, manipulating POs, and generating MOs. For those comfortable with a command shell.

Using POEdit as an example, open the po file created in Step 2 and do the translation. When done save the file as a compiled .mo file, this file will be used by PHPDevShell for its translation.

Where to move newly created .mo files?

You are almost done! The next few steps are the final steps in the translation process. This is where the .mo files needs to be moved so the translation system can locate and load the.

Enable FirePHP debugging in PHPDevShell, this will show debug information on where it is looking for .mo files. Note this location.

Now copy your .mo file from the language debug information to the correct directory, see below example:

plugins/DummyPlugin/language/af_ZA/LC_MESSAGES/ExamplePlugin.mo

Finally, you are done. I hope this process was not too confusing. If you have problems dont hesitate to ask using any of our support channels.

Errors, Exceptions and Debug

PHPDevShell offers three mechanisms which can sometimes confuse the new user because they seems similar at first sight:

  • Errors occurs when an unexpected problem cannot be solved by the program
  • Exceptions, is a standard mechanism allowing the program to handle special cases
  • Debug provides the user tools to interact with the program in order to fix bugs

These three mechanisms interact with each other so we will study all three here.


Errors, Exceptions and Debug

These are three separate, however related, concepts.

Errors

Errors occur when something goes wrong at the language level: a typo, an impossible action (division by zero), a type error... They are generated by the PHP engine. Depending on the configuration, they can be left alone (not wise, it will break the script), or handled by PHPDevShell and turned into an special exception called ErrorException.

You can divide errors into three generic categories: notices (a situation which is probably harmless), warning (a situation which is likely problematic) and critical (abandon all hopes). The usual policy is to display notices and keep the cycle running, and handle everything else as a dead end. You can configuration everything to suit your needs (see below).

Exceptions

Present in most modern languages, the goal of the exception mechanism is to deal with "special cases" out of the main stream of code. The advantages of using it are:

  • protection: any error occurring anywhere in the function or the functions it calls can be caught
  • code readability: the error handling code is distinct and doesn't mess with the main code
  • error versatility: many types of errors can be handled, even if they're not known from the developer
  • responsibility: the function can handle some errors and leave the other to someone else

You can read about PHP exception in the online manual. PHPDevShell allows and uses Exception everywhere in the code. A top-level exception handler is provided in case a thrown exception is not caught anywhere else.

Example (from PHPDevShell code):

try {
	$this->db->get_essential_settings();
} catch (Exception $e) {
	session_destroy();
	header('Location: install.php');
}

That means: we try to fetch the essential settings from the database. In case we fail, we assume we need install is needed.

Debug

The debugging modules provided by PHPDevShell are basically extended "error_log()" functions. They are not designed to substitute for PHP debuggers such as Zend Debug or XDebug. They use the conduits configured by the ExceptionHandler to send data to the developer.


The ExceptionHandler

The ExceptionHandler's job is to react to such situations by reporting them according to the site's configuration. The reports can be sent through various conduits and should contain as much information as possible to help the site's owner/developer to give the appropriate response.

Currently the following conduits are available:

  • on screen (on production site, it's better to let the user know an error occurred with no more information to avoid security leaks)
  • through FireBug/FirePHP (only for development)
  • by mail (to inform the site owner if he's not monitoring the site in real time)
  • on syslog

Each can be activated and configured separately.

Depending on the conduit, the report contains information such as the location of the error, the timestamp when it occured, the backtrace of the function which triggered the error, etc.


Overview

You can see how these interact in this picture:

Exceptions, Errors, Debug


Enabling and disabling

  • The exception mechanism is built in PHP so it cannot be disabled. If don't want to use it, just don't.
  • The error handler is part of PHPDevShell and cannot be disabled; however you can enable/disable each conduit, so if you disabled all conduits, you won't receive any message.
  • The debug can be disabled (useful for production sites) based on the concept of "domains" (refer to the documentation for debug).

Remember: on a production site, debug MUST be disabled but errors MUST be handled (nicely).


Configuration

The configuration files allow you to:

  • tell how you want error types to be handled (break on notices or not, on warnings, etc)
  • filter what kind of error you want to to be handled (it's usually a good thing to catch all errors)
  • set which conduits should sent the information where

Note: if a warning or a notice occurs when set to non-fatal, they won't be converted to exception and therefore won't be catchable. They will, however, be displayed (unless configured to be ignored).

fatal = false fatal = true
ignore = true not displayed, not catchable not displayed, not catchable
ignore = false displayed, not catchable not displayed, catchable

Usage

Let's see how we can actually use our little debugger.

Sending data to the conduits

Basic usage is very simple: just send a string to the log() method:

$this->log("Hello, console");

This method is defined in PHPDS_dependant so pretty much any code can access it.

Domains

All data sent thought the conduits are tagged with a semantic domain, such as "db" or "user", so only
the data you want to study are actually sent. The default behavior is to use the class name (with the PHPDS_ prefix riped off). For you own classes, you can change it to anything you fancy, maybe having something adaptative. Take a look at what PHPDS_query does:

	public function debugInstance($domain = null)
	{
		return parent::debugInstance(empty($domain) ? 'QUERY%'.get_class($this): $domaine);
	}

debug instances

Almost every object using the debug has a private debug instance ; this is mostly done to allow domain filtering, but you can override the debugInstance() method to provide your own customized debug instance or just customize the domain, like the Query system does.


Conduits

Data sent inside the debugger can be broadcast to various locations, depending on your configuration. Here are the default conduits available.

Display

This is the most detailed  way of displaying errors, and it's mostly used when the site is under development. It will display as much information as possible, including backtrace and code fragments.

Can be enabled or disabled.


$configuration['error']['display_errors'] = true;

FirePHP

Used only when developing, FirePHP is very clever way to debug a webpage. It's a plugin for Firebug which display some debug information sent along with the page (but not inside the page) by PHPDevShell. You can use it to study dynamic debug data without disturbing the page.

Can be enable or disabled.


$configuration['error']['firePHP'] = false;

Log file

This is the traditional conduit: writing textual data into a flat text file. Useful to keep hidden traces of a production site, but not interactive and very limited in the complexity of the data stored.

You can choose any file you have write access to.


$configuration['error']['file_log_dir'] = 'write/logs/';

Mail

A way to be alerted very fast when something goes wrong. Should be used only for production site, and for important errors.

You can set the recipient of the emails.


$configuration['error']['email_critical'] = 'yourmail@phpdevshell.org';


Early debug

Even if PHPDevShell's error and exception handler is installed early in the cycle, everything executed prior to this point cannot, obviously, be handled that way. To help catching error in this first part of the cycle a very limited debug channel is available by setting the global variable $early_debug to true. In that case, anything set to PHPDS->log() will be output to the web server's error log (via error_log() ), until the normal handlers are set.

Why several debug instances?

If you study how the debug object works, you'll notice it gets instantiated several times, and you may wonder why. I'll try here to explain why and what that allows.

To each its own

Most instances created by the Skel (actually, all daughters of PHPDS_dependant), have their own debug instance. Usually, the only difference is "domain" field, which by default is the class name. This is to enable the user to filter which messages he wants: only the domains listed in the config file are displayed (actually, if not enabled they are not even sent).

Setting a domain

Althought it's not supposed to change over time, you can set the domain of a debug object whenever you want with the domain($domain) method. Setting this will also enable or disable the debug object (based on the activated domains listed in the config file). Subsequently, all log calls will only produce output if the debug object is enabled.

Why so heavy?

You may wonder why I choosed to create a new object for each class. As usual the answer is: overrides! Let's say you're developing a sophisticated messaging system using an external server, you may want to add functionnalities to your debug object but only for your class. You just have to override the debug_instance() method of your class to create your custom debug object.

Suggestions on error

Note: this is about version 3.0.1, currently in dev as time of writing.

As you know the error handler (PHPDS_errorHandler) will catch any error or uncaught exception happening within your code. What you may not know is that you can provide more info / suggestions to the site admin when such errors arise.

To understand what you can do, take a look at this code:

class PHPDS_sprintfnException extends PHPDS_exception
{

	public function __construct($message = "", $code = 0, $previous = null) // CAUTION this declaration is NOT correct
	{
		$msg = '

The faulty string source is:
<pre class="ui-state-highlight ui-corner-all">'.htmlentities($message).'</pre>
'; if (!empty($code)) $msg .= 'The parameters were:<br />'.PU_dumpArray($code, null, true).'

'; parent::__construct($msg, 0, $previous); } public function hasCauses() { return true; } public function getCauses() { $result = array( 'Unable to build a string with sprintfn', array( array('Some template or theme file has altered a module which does comply to the given parameters.', 'Try a different theme or check for possible typos in the theme module list') ) ); return $result; } }

This exception is throw when an error occurs in the PU_sprintfn() utility function. Take a look at the getCauses() method. It simply returns an array: for each possible cause, it provides a suggestion about what's wrong and what you can do about it. The error handler will nicely format that and put it on the top of the error page.

This is especially usefull when you're writing code which will be used by others (such as plugin).

Team Development

Version Control

NOTE: Version control is not mandatory to understand in order to use PHPDevShell. This is for advanced users that want to use versioning control for their projects and/or have teams that will be working on it.

Before you start, learn more by reading and understanding this: https://help.launchpad.net/Code It is very important that you go through the above pages. It is short and to the point like everything else in Launchpad. Don't you just love the Canonical Developers?

Learn more about Bazaar in this tutorial: http://doc.bazaar.canonical.com/latest/en/mini-tutorial/

The combination of Bazaar branch hosting and Launchpad's teams infrastructure gives you a very powerful capability to collaborate on code. Essentially, you can push a branch into a shared space and anyone on that team can then commit to the branch.

This means that you can use Bazaar in the same way that you would use something like SVN, i.e. centrally hosting a branch that many people commit to. You have the added benefit, though, that anyone outside the team can always create their own personal branch of your team branch and, if they choose, upload it back to Launchpad.

However, Bazaar is flexible enough that you can choose your own workflow.

Ready to start working

For the next chapter you need to understand this https://help.launchpad.net/Code/TeamBranches

PHPDevShell uses the "Decentralised with shared main line" module to share code.

This is a simple method allowing you to work locally and when you have a stable piece of code you can merge it back into the main team branch, when the rest of the team updates they will find that PHPDevShell is still running with your updates included.

Note : Bazaar allows you to push the same branch to any repository in your personal account or the PHPDevShell team account.

Assuming you just started first look at what branch is the latest team branch being worked on here https://code.launchpad.net/~phpdevshell see the highest version number and use this branch. When clicking on the branch link it will show you what the commands are to pull and push this particular branch, in this case:

bzr branch lp:~phpdevshell/phpdevshell/vx.x.x

You can pull this branch into multiple directories allowing to push to experimental, development, main branch etc. making sure that the main branch, in this case "release-x.x.x" is always running code, meaning that PHPDevShell still works with this code.

To recap, I have created the following local directories for myself doing "bzr branch lp:~phpdevshell/phpdevshell/vx.x.x" in each directory, this will be used in the examples below; /experimental [experimental tests] /development [running code to merge into team development branch release-x.x.x] /projects [private projects I run on phpdevshell] /extra_whateva_folders

Scenario Examples

First and something important to NOTE

Always keep your local work up to date with the latest team branch, doing this will prevent major conflicts and assure that everyones code is compatible. When you do a merge the merge will be done locally, so do not be scared to do it.

In all scenarios you should always first merge latest development repository changes into your local development folders if it was not a freshly pulled branch;

brz commit -m "My new changes blah, blah..."
bzr merge lp:~phpdevshell/phpdevshell/vx.x.x

Now everything is merged in your code locally, when you are ready to push the merged code back into the development "release-x.x.x" branch do;

brz commit -m "My new merged code"
bzr push lp:~phpdevshell/phpdevshell/vx.x.x

Now the team branch is merged with your code available to everyone.

If you found that bad correspondence made more then 1 person work on the same lines of code, you will find conflicts, either ask the team project leader to resolve these conflicts or resolve it by;

Getting down to business

Handling new team development changes locally for "official" team repository.

Working in your "development" folder you have made working team changes and you are ready to push this into the team branch "release-x.x.x", follow above example Scenario Examples exactly to achieve this.

Experimental Code for possible implementation

You want to try something new for PHPDevShell, which could either be a disaster or could end up as official code. So working in your "experimental" folder you do those changes.

  1. While working you could push this to any branch auto registering that branch:
bzr push lp:~phpdevshell/phpdevshell/experiment/vx.x.x

or push it to you own private repos

bzr push lp:~username/phpdevshell/experiment/vx.x.x

If you see it did not you don't have to do anything else, if it did work simply merge this experimental folder with the latest team development repository "release-x.x.x" by following the example Scenario Examples.

Know your flexibility

Because Bazaar is so flexible, and allows you to push the same local directory to any remote location without registering it for example :

bzr push lp:~username/+junk/just-a-test

You do not have to stick tot the rules, you can have your own main branch kept up to date from the team "experiment-x.x.x" branch with your code changes added daily, then when you are ready some day you can request it to be merged, or if you are a team developer you can merge it yourself and no conflicts will exist.

The ONE thing to REMEMBER

Keep your local development folder up to date with the remove team development repository!

BZR Authentication problems

If you experience any connection or authentication problems and you have done the required steps in Launchpad. If the errors looks something like below:

sudo bzr push lp:~phpdevshell/phpdevshell/v2.8.2
Permission denied (publickey).                                                 
bzr: ERROR: Connection closed: please check connectivity and permissions 

Try doing this;

bzr launchpad-login username
bzr push bzr+ssh://username@bazaar.launchpad.net/~phpdevshell/phpdevshell/v2.8.2

Auto Code Completion in PHPDevShell

article_Screenshot_3.png

As we get lazier (or older) we appreciate the value of code completion. PHPDevShell does support code completion but at this stage only in Netbeans. Because PHPDevShell uses allot of lazy loading very few other IDE's understand it. Yet our favorite IDE does. So if you really want code completion, you will have to get used to Netbeans.

Code completion works with the whole core, so anything will auto complete. To make autocomplete work on plugins you just called, you will need to add a simple comment;

		/* @var $crud crud */
		$crud = $this->factory('crud');

		/* @var $spam spamAssassin */
		$spam = $this->factory('spamAssassin');

		// Now typing $spam->... will autocomplete.

In the two examples above both vars $crud and $spam will auto complete.

Code of Conduct

It is the 'belief in a universal bond of sharing that connects all humanity'.

The same ideas are central to the way the PHPDevShell community collaborates. Members of the PHPDevShell community need to work together effectively, and this code of conduct lays down the ground rules for our cooperation.

In the free software world, we collaborate freely on a volunteer basis to build software for everyone's benefit. We improve on the work of others, which we have been given freely, and then share our improvements on the same basis.

That collaboration depends on good relationships between developers. To this end, we've agreed on the following code of conduct to help define the ways that we think collaboration and cooperation should work.

This code of conduct covers our behaviour as members of the PHPDevShell Community, in any forum, mailing list, wiki, website, Internet relay chat (IRC) channel, install-fest, public meeting or private correspondence. PHPDevShells governance bodies are ultimately accountable to the PHPDevShell Community Council and will arbitrate in any dispute over the conduct of a member of the community.

  • Be considerate. Our work will be used by other people, and we in turn will depend on the work of others. Any decision we take will affect users and colleagues, and we should take those consequences into account when making decisions. Even if it's not obvious at the time, our contributions to PHPDevShell will impact the work of others. For example, changes to code, infrastructure, policy, documentation and translations during a release may negatively impact others' work.
  • Be respectful. The PHPDevShell community and its members treat one another with respect. Everyone can make a valuable contribution to PHPDevShell. We may not always agree, but disagreement is no excuse for poor behaviour and poor manners. We might all experience some frustration now and then, but we cannot allow that frustration to turn into a personal attack. It's important to remember that a community where people feel uncomfortable or threatened is not a productive one. We expect members of the PHPDevShell community to be respectful when dealing with other contributors as well as with people outside the PHPDevShell project and with users of PHPDevShell.
  • Be collaborative. Collaboration is central to PHPDevShell and to the larger free software community. We encourage individuals and teams to work together whether inside or outside the PHPDevShell Project. This collaboration reduces redundancy, and improves the quality of our work. Internally and externally, we should always be open to collaboration. Wherever possible, we should work closely with upstream projects and others in the free software community to coordinate our efforts in all areas whether they be technical, advocacy or documentation. Our work should be done transparently and we should involve as many interested parties as early as possible. If we decide to take a different approach than others, we will let them know early, document our work and inform others regularly of our progress.
  • When we disagree, we consult others. Disagreements, both social and technical, happen all the time and the PHPDevShell community is no exception. It is important that we resolve disagreements and differing views constructively and with the help of the community and community processes. There are also several project teams and team leaders, who may be able to help us figure out the best direction for PHPDevShell. When our goals differ dramatically, we encourage the creation of alternative sets of packages, or derivative.
  • When we are unsure, we ask for help. Nobody knows everything, and nobody is expected to be perfect in the PHPDevShell community. Asking questions avoids many problems down the road, and so questions are encouraged. Those who are asked questions should be responsive and helpful. However, when asking a question, care must be taken to do so in an appropriate forum.
  • Step down considerately. Members of every project come and go and PHPDevShell is no different. When somebody leaves or disengages from the project, in whole or in part, we ask that they do so in a way that minimises disruption to the project. This means they should tell people they are leaving and take the proper steps to ensure that others can pick up where they left off.

This code of conduct was compiled using the Ubuntu Code of Conduct. We thank them for their openness and friendliness towards us allowing us to re-use it.

Coding Standards

The PHPDevShell Coding Standards apply to code that is part of the official PHPDevShell distribution. Coding standards often abbreviated as CS among developers and they aim to keep code consistent to be easily readable and maintainable by most of PHPDevShell folks.

This documentation is based on Drupals code of conduct which is based on Pear's code of conduct.

Naming Plugin Files

Naming your plugin files is relatively straight forward (my-controller as example).

For general plugin folders :

MyPlugins

For general plugin controllers :

my-controller.php

Models will adapt same name :

my-controller.query.php

Views will adapt same name :

my-controller.tpl

Class file names should be :

someFeature.class.php

PHP Code Tags

Always use <?php ?> to delimit PHP code, not the shorthand, <? ?> = WRONG. This is required for PHPDevShell compliance and is also the most portable way to include PHP code on differing operating systems and set-ups.

It is better not to use closing ?> tags at the end of the file. This prevents spaces or line breaks being printed out causing headers already sent errors.

Indenting and Whitespace

Use an indent type TABS with size 4 tabs per indent.

Lines should have no trailing whitespace at the end.

NEVER start your first line of code right after first <?php indented.

Files should be formatted with \n as the line ending (Unix line endings), not \r\n (Windows line endings).

All text files should end in a single newline (\n). This avoids the verbose "\ No newline at end of file" patch warning and makes patches easier to read since it's clearer what is being changed when lines are added to the end of a file.

Operators

All binary operators (operators that come between two values), such as +, -, =, !=, ==, >, etc. should have a space before and after the operator, for readability. For example, an assignment should be formatted as $foo = $bar; rather than $foo=$bar;. Unary operators (operators that operate on only one value), such as ++, should not have a space between the operator and the variable or number they are operating on.

Casting

Put a space between the (type) and the $variable in a cast: (int) $mynumber.

Concatinate

Have spaces before and after concatination character;

<?php
$hello_world = "I would like " . $to . ' say ' . HELLO_WORLD;
?>

Control Structures

Control structures include if, for, while, switch, etc. Here is a sample if statement, since it is the most complicated of them:

if (condition1 || condition2) {
  action1;
} else if (condition3 && condition4) {
  action2;
} else {
  defaultaction;
}

Control statements should have one space between the control keyword and opening parenthesis, to distinguish them from function calls.

You are strongly encouraged to always use curly braces even in situations where they are technically optional. Having them increases readability and decreases the likelihood of logic errors being introduced when new lines are added.

For switch statements:

switch (condition) {
    case 1:
        action1;
        break;
    case 2:
        action2;
        break;
    default:
        defaultaction;
}

// For do-while statements:
do {
    actions;
} while ($condition);

Function Calls

Functions should be called with no spaces between the function name, the opening parenthesis, and the first parameter; spaces between commas and each parameter, and no space between the last parameter, the closing parenthesis, and the semicolon. Here's an example:

$var = foo($bar, $baz, $quux);

As displayed above, there should be one space on either side of an equals sign used to assign the return value of a function to a variable. In the case of a block of related assignments, more space may be inserted to promote readability:

$short = foo($bar);
$long_variable = foo($baz);

Function Declarations

function funstuff_system($field) 
{
    $system["description"] = __("This is some funny description.");
    return $system[$field];
}

Arguments with default values go at the end of the argument list. Always attempt to return a meaningful value from a function if one is appropriate.

Class Constructor Calls

When calling class constructors with no arguments, always include parentheses:

$foo = new myClassName();

This is to maintain consistency with constructors that have arguments:

$foo = new myClassName($arg1, $arg2);

Note that if the class name is a variable, the variable will be evaluated first to get the class name, and then the constructor will be called. Use the same syntax:

$bar = 'myClassName';
$foo = new $bar();
$foo = new $bar($arg1, $arg2);

Variables and Arrays

Arrays should be formatted with a space separating each element (after the comma), and spaces around the => key association operator, if applicable:

$some_array = array('hello', 'world', 'foo' => 'bar');

Note that if the line declaring an array spans longer than 80 characters (often the case with form and menu declarations), each element should be broken into its own line, and indented one level:

$form['title'] = array(
  '#type' => 'textfield',
  '#title' => __('Title'),
  '#size' => 60,
  '#maxlength' => 128,
  '#description' => __('Some description.')
);

Multidimentional arrays should also be named lowercase with underscores.

$simple_array['my_array'] = 'Some Value';

For normal variables even inside a class (class properties are lowerCamelCase.)

$my_variable = 'Value';
$foo_bar = 'Another Variable';

Naming Conventions

Functions

Functions and variables should be named using lowercase, and words should be separated with an underscore. Functions should in addition have the grouping/module name as a prefix, to avoid name collisions between modules.

function foreach_example()
{
	$arr = array(1, 2, 3, 4, "b" => 5, "a" => 6);
	foreach ($arr as &$value) {
		$value = (int) $value * 2;
	}
}

Constants

Constants should always be all-uppercase, with underscores to separate words. This includes pre-defined PHP constants like TRUE, FALSE, and NULL. Module-defined constant names should also be prefixed by an uppercase spelling of the module they are defined by.

Global Variables

If you need to define global variables, their name should start with a single underscore followed by the module/theme name and another underscore.

Classes

Classes should be named using "camelCase." For example:

class someProperClassName 
{
	// Code
}	

Class methods and properties should use "lowerCamelCase":

class someProperClassName
{
	public $someProperty;
	protected $anotherProperty = true;
	
	public function ifExample($a, $b)
	{
		$this->anotherProperty = 'Value';
		if (convert($a) > $b) {
			echo "a is bigger than b";
		} elseif ($a == $b) {
			echo $a . " is equal to " . $b[0];
		} else {
			echo $this->property;
		}
		$result = $a < $b ? $a : $b;
	}

	public function forExample()
	{
		for ($i = 1; $i <= 10; $i++) {
			echo 'Item: ';
			echo $i;
		}
	}

	public function foreachEample()
	{
		$arr = array(1, 2, 3, 4, "b" => 5, "a" => 6);
		foreach ($arr as &$value) {
			$value = (int) $value * 2;
		}
	}

	public function whileExample()
	{
		$i = 1;
		while ($i <= 10) {
			echo $i++;
		}
	}

	public function doWhileExample($i)
	{
		do {
			echo $i--;
		} while ($i > 0);
	}

	public function switchExample()
	{
		switch ($i) {
			case 0:
				echo "i equals 0";
				break;
			case 1:
				echo "i equals 1";
				break;
		}
	}

	public function tryExample()
	{
		try {
			echo inverse(5) . "\n";
		} catch (Exception $e) {
			echo 'Caught exception: ' . $e->getMessage() . "\n";
		}
	}
}

PHPDevShell exclusive and unique class names for the main core classes should use "PHPDS_lowerCamelCase", you will probably never use this as these classes are all already created :

class PHPDS_someProperClassName
{
	// Some code...
}

The use of private class methods and properties should be avoided -- use protected instead, so that another class could extend your class and change the method if necessary. Protected (and public) methods and properties should not use an underscore prefix, as was common in PHP 4-era code.

Quotes

PHPDevShell does not have a hard standard for the use of single quotes vs. double quotes. Where possible, keep consistency within each module, and respect the personal style of other developers.

With that caveat in mind: single quote strings are known to be faster because the parser doesn't have to look for in-line variables. Their use is recommended except in two cases:

  1. In-line variable usage, e.g. "<h2>$header</h2>".
  2. Translated strings where one can avoid escaping single quotes by enclosing the string in double quotes. One such string would be "He's a good person." It would be 'He\'s a good person.' with single quotes. Such escaping may not be handled properly by .pot file generators for text translation, and it's also somewhat awkward to read.

Queries

PHPDevShells unique model and query system allows you to split data from controller. Queries needs to be neat and ordered to make it as easy to read as possible. This can be achieved by having a line break after each database function and then indenting the next line. Each data querie needs to be in its own class, a class with typical formatting will look like this:

/**
 * Menu Item Admin - Get All Templates
 * @author Jason Schoeman [titan@phpdevshell.org], Ross Kuyper, Contact: rosskuyper@gmail.com.
 *
 */
class PluginName_getAllTemplatesListQuery extends PHPDS_query {
	protected $sql = "
		SELECT
			template_id, template_folder
		FROM
			_db_core_templates
		ORDER BY
			template_folder
	";
	
	/**
	 * Initiate query invoke command.
	 * @return array
	 */
	public function invoke(){
		$select_template = parent::invoke();
		
		foreach ($select_template as $select_template_array) {
			$template_option[$select_template_array['template_id']] = $select_template_array['template_folder'];
		}
		
		return $template_option;
	}
}

To call a query model:

Always use your plugin name infront of the query so one knows where the query comes from, extend the name with "Query" at the end. Something like this:

Comments

Inline documentation for source files should follow the Doxygen formatting conventions.

Comments are strongly encouraged, this is the most important part for people to follow your code. A general rule of thumb is that if you look at a section of code and think "Wow, I don't want to try and describe that", you need to comment it before you forget how it works.

Non-documentation comments should use capitalized sentences with punctuation. Sentences should be separated by single spaces. All caps are used in comments only when referencing constants, for example TRUE. Comments should be on a separate line immediately before the code line or block they reference. For example:

// Unselect all other contact categories.
$this->db->someMethod();

If each line of a list needs a separate comment, the comments may be given on the same line and may be formatted to a uniform indent for readability.

Here is an example of commented code:

/**
 * This is a new version of one the Big5: the security class
 *
 * This new version supports connectors and queries class and should be compatible with the old one
 *
 * @version	1.0
 * @date 20100219
 * @author greg
 */
class PHPDS_security extends security 
{
	/**
	 * Cleaned up $_COOKIE.
	 *
	 * @var mixed
	 */
	public $cookie;

	/**
	 * This method does the actual security check, other security checks are done on a per call basis to this method in specific scripts.
	 * Improved version reduces the cost of queries by 3, I also believe that this is a more secure method.
	 *
	 * @param boolean $validate_crypt_key Set if you would like the system to verify an encryption before accepting global $_POST variables. Use with method send_crypt_key_validation in your form.
	 * @return string
	 * @author Jason Schoeman
	 */
	public function securityIni($validate_token = false) 
	{
		$this->_log(sprintf(___('Security check for user id %s'), $_SESSION['user_id']));

		// Do last security checks before allowing the script to execute.
		// Check if user can see this menu item.
		if (!empty($_GET['menu_id']) && !empty($this->navigation->navigation[$_GET['menu_id']]['menu_id'])) {
			$gm = $_GET['menu_id'];
			$sm = $this->navigation->navigation[$_GET['menu_id']]['menu_id'];
		} else {
			$gm = false;
			$sm = false;
		}
		// Show error if user does not have permission.
		if ($gm != $sm) {
			$this->template->stopScript = ___('SECURITY ALERT, There seems to be a security limitation, you are not allowed to run this script or the script could not be found. Make sure your session did not expire, you might be logged out.');
		}
	}
}

C style comments (/* */) and standard C++ comments (//) are both fine, though the former is discouraged within functions (even for multiple lines, repeat the // single-line comment). Use of Perl/shell style comments (#) is discouraged.

Including Code

Anywhere you are unconditionally including a class file, use require_once(). Anywhere you are conditionally including a class file (for example, factory methods), use include_once(). Either of these will ensure that class files are included only once. They share the same file list, so you don't need to worry about mixing them - a file included with require_once() will not be included again by include_once().

Note: include_once() and require_once() are statements, not functions. You don't need parentheses around the file name to be included.

This article will describe the guidelines and advises on how code should be written in the PHPDevShell environment. Following these guidelines make the code easier to read, and therefore to understand and maintain. It also ensures good compatibility with PHPDoc. By conforming to the same standards, you will make your life easier!

Thesaurus

Namespace prefix: namespaces are a recent feature of PHP; as it's not production-ready, we decided to use manual setting of name spaces. Practically, it means that every named entity which will evolve in the global execution environment (classes, globals, functions) should have a name beginning with a unique prefix, usually a code name for the plugin. Every PHPDevShell entity starts with "PHPDS_"; test-related classes with "TEST_".

PHPDoc: PHPDoc is a program which reads the source code of a project and compiles documentation out of it. We use it to create API documentation, so every file, class, field, and method is preceded by a comment bloc following PHPDoc structure.

As a special case, a method which is NOT meant to be used by someone else (for example, a callback function for a loop), can be written with all lower cases plus underscores. Furthermore, you should NEVER call a method who's name starts with an underscore.

Comments & PHPDoc

Programmer's comments

Use descriptive comments in functions' body but don't overload the code.

Use a standard header at the top of each file, stating all needed information: author, date, version of PHPDevShell compatible, package, dependencies...

PHPDoc

Add PHPDoc info before each class declaration, field (class variable) and method (class function); don't use tabs in those.


/**
 * First line is a short presentation of the function (used with using collapse in IDE)
 *
 * Next few lines is a detailed description, including guidelines and maybe a short example
 *
 * @param type $param1 what is this parameter 1
 * @param type $param2 what is this parameter 2
 * @date 20100412 (v1.0.1) (me) change description for the date/version
 * @date 20090615 (v1.0) (me) creation
 * @version 1.0.1
 * @author me
 *
 * @return type what is returned - can be "nothing"
 */

Code presentation

To improve readability and reduce bug risk, your functions must be small; if you have more than 20 lines, you probably better split it up. And don't forget overriding: a method should do ONE thing, so it can be overridden (and tested) easily.

It's also better to avoid wide lines: 80 to 100 characters should be considered too wide, add CRs if possible.

  • don't use space where they are not needed, use them only to improve readability
  • put the opening bracket (if, foreach, switch...) at the end of the line, except for function bodies
  • use single quote strings if interpretation is not needed
  • when using string compare "==", try to put the constant before and the variable after
  • never use a non-defined variable except with function which can handle it (empty(), isset(), ...) ; as a general case always define the variables you will use with default value (even "null" if you want)
  • after a if, while, foreach, ... try to always use a bracketed code block
  • when using a direct value, always consider using a parameter with a default

Misc

  • When in doubt, use standard Eclipse formatting.
  • Don't add closing tag ?> at the end of you php files.
  • Never overlook unit testing, it may save your vacations (you know production code ALWAYS break during your vacations).
  • If you have question, consult the community in the group!

Terminology

This page gather several small topics which aren't not big enough to get a full page. They usually detail a feature or a term used elsewhere in several places.

Class sneaking

PHPDS provides you with a nice feature: class sneaking, a way to use classes without include'ing their source files.

It works by overriding the default autoloader of PHP, which is called everytime the new() operator is called for a class which is not defined. In such as case, PHPDS looks in several places for a file named after the class name ; if found, the file is include'd so the new() operator can instantiate the class.

The folders are:

  • the site main include/ folder
  • the current plugin include/ folder
  • the include/ folder of the PHPDevShell plugin
  • the include/ folder of the plugin_alt field of the plugin

Note: "include_once" is used, which means a missing file generates no error as such, and a present file is included only once. That also means any class described in this file will be then defined, and therefore the new() operator will not trigger the autoloader.

Be careful that the given class name is used to build the filename, which on modern OSes is case sensitive. So be carefull if you develop on case-insensitive filesystems and deploy on case-sensitive filesystems.

Note: all class-related functions (such as "class_exists()") will also trigger the autoloader if the class is not defined.

A skeleton in the closet

From the beginning PHPDevShell has 5 major classes which we refer to as the Big5. When we moved to OOP, we introduced a "main instance" responsible of instantiating the Big5. After some thoughts, this main instance has been baptised as Skel (short for skeleton of course). Although it's named "PHPDS" in the sources, we will refer to it as Skel from now on in the documentation.
Note that this name will also be used when tagging debug messages.

The Big5

Early in the development process of PHPDevShell, it appeared that 5 class make the foundation of the software: core, db, navigation, security, template. They contain most of the needed functions and have one instance each, created very early during initialization. We call them the Big5. As PHPDevShell evolved, two other appeared: debug and errorHandler, along with two important arrays: configuration and lang.

The instantiation of all theses elements can be overridden in the main class, PHPDS. With the obvious exception of configuration, they also can be overridden by plugins.

Icons for menus

Menus which appear in the Dashboard can have custom icons in a very simple way: just add a png file in the "images" folder of the plugin. This png file should be 48x48 pixels, and bear the name of the php file it represents. Note that only regular menu items are supported for now.

Typography in Articles/Docs

This is a H1 Header

Editors and Documentation writers have a nice set of standard HTML typography at their disposal to write neat documents, just follow the guides below. You must set filters to Full HTML to use these.

This is a H2 Header

Mauris et dapibus ipsum. Suspendisse aliquam ullamcorper eros ut sodales. Aenean lobortis, felis quis cursus convallis, augue nulla volutpat velit, in scelerisque erat mauris eu mauris.

This is a H3 Header

Fusce enim dolor, ullamcorper eget scelerisque eget, facilisis vel metus. Donec nec dolor lorem, nec sollicitudin massa. Vestibulum pulvinar porttitor dolor, vel congue neque aliquam vitae.

phpds3_1.png

This is a H4 Header

Adding images to article has never been easier. Simply click on Insert image link below text window. After upload is complete double click on the image and a link will be insterted. Now simply edit your link that was inserted in your centent and add class="left framed" to nicely wrap your image.

This is a H5 Header

Phasellus consectetur magna eu risus rhoncus faucibus. Nulla dictum nisl eget felis tempus ullamcorper. Vestibulum in orci nec libero blandit lobortis at ut dolor. Duis et nisl fringilla enim pharetra vehicula.

Drop Caps

Drop caps. Apply <span class="typo-dropcap">D</span> for first letter. Phasellus ut orci rutrum justo aliquet eleifend. Phasellus consectetur magna eu risus rhoncus faucibus. Nulla dictum nisl eget felis tempus ullamcorper. Vestibulum in orci nec libero blandit lobortis at ut dolor. Duis et nisl fringilla enim pharetra vehicula. Duis posuere dui in nisi molestie egestas sed a lacus. Nam id urna elit, vitae molestie magna. Phasellus vitae ipsum nulla, nec mattis sapien.

Code

/*
* Override filter.module's theme_filter_tips() function to disable tips display.
*/
function lotus_filter_tips($tips, $long = FALSE, $extra = '') {
  return '';
}

function lotus_filter_tips_more_info () {
  return '';
}  

Apply <pre class="brush: php">...</pre>

Replace "php" with other languages like : javascript, sql, xml.
So if you want to display JavaScript code use brush: javascript
If you want to display XML code use brush: xml , etc.

To have HTML coding, you need to add <pre class="brush: php; html-script: true">...</pre>. However sometimes this is not enough as the code highlighter still does not like html. You might want to convert html open tags to its html character set like this:

&lt;html>

Some HTML

Inset

Aenean hendrerit semper mollis. Nullam egestas viverra mauris, non bibendum turpis imperdiet vel. Morbi elementum dignissim mattis. Nulla scelerisque, massa a consectetur scelerisque, odio enim dignissim risus, in laoreet orci metus quis urna. Mauris gravida aliquam urna ut vehicula.

Apply <blockquote class="typo-inset-left"> ... </blockquote> Ut tempor neque quis enim.

Ut vestibulum consectetur pharetra. Nunc eu nisl sapien. Vestibulum ac erat tellus, nec blandit nibh. Integer mollis faucibus diam ut volutpat. In ut dolor vel ligula tincidunt consectetur eget ac eros.

Praesent consectetur, nibh id sollicitudin varius, velit dolor faucibus metus, ut blandit purus ligula vitae lacus. Pellentesque in rutrum dui. Mauris vel dui diam, nec suscipit elit. Proin mollis, leo eu aliquam porttitor, magna velit tincidunt sapien, sed fermentum justo nisi non quam.

Apply <blockquote class="typo-inset-right"> ... </blockquote> Proin fringilla facilisis libero.

Sed orci. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Sed euismod magna a nibh. Sed euismod magna a nibh. Praesent rutrum sapien ac felis. Phasellus elementum dolor quis turpis. Vestibulum nec mi vitae pede tincidunt nonummy.

Praesent rutrum sapien ac felis. Phasellus elementum dolor quis turpis. Vestibulum nec mi vitae pede tincidunt nonummy. Vestibulum facilisis mollis neque. Sed orci. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.Praesent rutrum sapien ac felis. Phasellus elementum dolor quis turpis. Vestibulum nec mi vitae pede tincidunt nonummy.

Quote

This is a sample quote. Apply <blockquote class="typo"><span class="open">T</span>... to the first letter. Apply <span class="close">r</span></blockquote> to the last letter.

Badge

01Apply <p class="typo-badge"><span class="orange">01</span>...</p> Phasellus viverra tempus arcu ut fringilla. Praesent molestie convallis ligula at consectetur. Quisque quis enim velit, eget posuere urna. Nullam porta cursus mi, vehicula convallis sapien vestibulum sed. Phasellus id felis nulla, non pulvinar nunc. Morbi feugiat bibendum lobortis.

02Apply <p class="typo-badge"><span class="red">02</span>...</p>Phasellus viverra tempus arcu ut fringilla. Praesent molestie convallis ligula at consectetur. Quisque quis enim velit, eget posuere urna. Nullam porta cursus mi, vehicula convallis sapien vestibulum sed. Phasellus id felis nulla, non pulvinar nunc. Morbi feugiat bibendum lobortis.

03Apply <p class="typo-badge"><span class="blue">03</span>...</p>Phasellus viverra tempus arcu ut fringilla. Praesent molestie convallis ligula at consectetur. Quisque quis enim velit, eget posuere urna. Nullam porta cursus mi, vehicula convallis sapien vestibulum sed. Phasellus id felis nulla, non pulvinar nunc. Morbi feugiat bibendum lobortis.

Custom List

  • <ul class="typo-checklist">
  • Praesent molestie convallis
  • Quisque quis enim velit eget
  • Nullam porta cursus
  • <ul class="typo-star">
  • Praesent molestie convallis
  • Quisque quis enim velit eget
  • Nullam porta cursus
  • <ul class="typo-arrow">

  • Praesent molestie convallis
  • Quisque quis enim velit eget
  • Nullam porta cursus

Message

Apply <div class="typo-alert">...</div> Cras elementum lacus ac velit tincidunt vel vestibulum urna viverra. Sed varius tempus turpis, tincidunt fermentum metus rhoncus a. Integer hendrerit elit in massa lacinia vestibulum. Mauris imperdiet ligula sit amet odio pellentesque tincidunt at eget magna. Ut non libero odio, tristique ullamcorper sapien.

Apply <div class="typo-favorite">...</div> Cras elementum lacus ac velit tincidunt vel vestibulum urna viverra. Sed varius tempus turpis, tincidunt fermentum metus rhoncus a. Integer hendrerit elit in massa lacinia vestibulum. Mauris imperdiet ligula sit amet odio pellentesque tincidunt at eget magna. Ut non libero odio, tristique ullamcorper sapien.

Apply <div class="typo-help">...</div> Cras elementum lacus ac velit tincidunt vel vestibulum urna viverra. Sed varius tempus turpis, tincidunt fermentum metus rhoncus a. Integer hendrerit elit in massa lacinia vestibulum. Mauris imperdiet ligula sit amet odio pellentesque tincidunt at eget magna. Ut non libero odio, tristique ullamcorper sapien.
Apply <div class="typo-info">...</div> Cras elementum lacus ac velit tincidunt vel vestibulum urna viverra. Sed varius tempus turpis, tincidunt fermentum metus rhoncus a. Integer hendrerit elit in massa lacinia vestibulum. Mauris imperdiet ligula sit amet odio pellentesque tincidunt at eget magna. Ut non libero odio, tristique ullamcorper sapien.
Apply <div class="typo-tip">...</div> Cras elementum lacus ac velit tincidunt vel vestibulum urna viverra. Sed varius tempus turpis, tincidunt fermentum metus rhoncus a. Integer hendrerit elit in massa lacinia vestibulum. Mauris imperdiet ligula sit amet odio pellentesque tincidunt at eget magna. Ut non libero odio, tristique ullamcorper sapien.
Apply <div class="typo-announcement">...</div> Cras elementum lacus ac velit tincidunt vel vestibulum urna viverra. Sed varius tempus turpis, tincidunt fermentum metus rhoncus a. Integer hendrerit elit in massa lacinia vestibulum. Mauris imperdiet ligula sit amet odio pellentesque tincidunt at eget magna. Ut non libero odio, tristique ullamcorper sapien.