History of CreatingServices

This page contains two tutorials on how to create Liberty Services. The first explains how to create a Service from scratch, while the second explains how to add Services to an existing bitweaver Package. If you are not familiar with what Services are or what they are good for, please first read: ((LibertyServices)). If you are not familiar with bitweaver ((bitweaverPackage|Packages)), you might want to read up on what those are also, though this tutorial also gives some basic instruction about creating a Package for the first time.

!Creating A Liberty Service Package
{maketoc}
!!Overview
Creating a service is very similar to creating a regular ((bitweaverPackage)). This tutorial will take you though setting up the minimal files you need to create a simple service and register it with the ((LibertyPackage|Liberty CMS Engine)).

This tutorial mostly uses the GeoPackage for its examples. The Geo Package is very simple but clearly shows the elegance and power of Liberty Services. Via Liberty Services the Geo Package makes it easy to attach location data to any type of content in bitweaver without having to modify any of the packages! So, wiki pages, blog posts, articles, users, and any new content types that might come along, like events for example, can have latitude and longitude coordiates associated with it. Via Liberty Services any Package then can automagically store this geo-spacial data, look it up, or remove it.

!!Files You Need
Like setting up any bitweaver package, the first thing you need is a directory in you bitweaver folder which is the name of your package. The convention is to give this directory a lowercase name:

{attachment id=389}

Inside your package directory you will need a few more directories and a couple of files. The directories you will need are __admin__ and __templates__.

{attachment id=390}

In your package directory also add the file __bit_setup_inc.php__ and your package class file following this naming convention: __LibertyYourpackage.php__. All bitweaver packages use a class to register themselves with the CMS Engine, the package class file is then, obviously where this code will go. If your package is only a service the bitweaver convention is to prefix your class file will 'Liberty'; if your package is a regular package creating a new content type like wiki or aritcles, then the convention is to prefix the file with 'Bit', e.g. __BitYourpackage.php__. The Geo Package is a service only package, so it's class file name is LibertyGeo.php.

{attachment id=391}

There are a few more files to create that you will need. In admin create __schema_inc.php__. In templates you will create any templates you wish to provide as a service. Lets not create any for now. This tutorial will explain how service templates are used by Liberty a little later on, and we will create one then.

{attachment id=392}

!!Creating Your Service Database Table
If you are creating a service, there probably will be some sort of data you want to store. Setting up your data table is the same for services as it is for packages. Since there is not that much specific about this for services, we will go over creating a data table quickly first to get it over with and then get into creating the service.

In all bitweaver Packages we define our data tables in the __schema_inc.php__ inside the package __admin__ folder. This is what the Geo Package schema_inc.php file looks like:

{code}
<?php
$tables = array(
'geo' => "
content_id I4 NOTNULL,
lat F,
lng F,
amsl F,
amsl_unit C(2)
CONSTRAINT ', CONSTRAINT `geo_ref` FOREIGN KEY (`content_id`) REFERENCES `".BIT_DB_PREFIX."liberty_content`( `content_id` )'
"
);

global $gBitInstaller;

foreach( array_keys( $tables ) AS $tableName ) {
$gBitInstaller->registerSchemaTable( GEO_PKG_NAME, $tableName, $tables[$tableName] );
}

$gBitInstaller->registerPackageInfo( GEO_PKG_NAME, array(
'description' => "A simple Liberty Service that any package can use to store geographic data (latitude, longitude, and above mean sea level) for any content.",
'license' => '<a href="http://www.gnu.org/licenses/licenses.html#LGPL">LGPL</a>',
) );
?>
{/code}

In the __$tables__ array we define each data table we want. Because Geo Package adds location data to all Liberty Content the table also includes a content_id value; this value is constrained to the same in the liberty_ccontent table. The rest of this file tells the installer to use this array to generate tables in our database and it also tells the installer some information about the package. The description and license information at the end is what is displayed in the installer and admin panel package listings so that others know what your package is all about.

Now that we have a place to store our data, we can get on to the good stuff...

!!Registering With Liberty Services
!!!Get a Liberty Service GUID
To register our service we first need to define a Liberty Service GUID in Liberty for our particular service. You can check the master list of taken Liberty Service GUID's on the ((LibertyServices)) page, or look in the file __LibertySystem.php__ in the __liberty__ package directory. You can either create a new GUID or associate your package with an existing one. __But be aware that existing Service GUID are used by other packages, and only ONE package can register with a service.__ You should only use an existing GUID if you are creating an alternate service to an existing one.

For Geo Package a new GUID was added to LibertySystem.php:

{code}
define( 'LIBERTY_SERVICE_GEO', 'global_positioning' );
{/code}

Now that we have a GUID to register to, we can finally write some code in the package.

!!!Register Your Service Functions
In __bit_setup_inc.php__ we will register our service to our Liberty Service GUID. We also need to add a few other things to this file. Lets look at bit_setup_inc.php from the Geo Package and walk through the parts we need to include:

{code}
<?php
$registerHash = array(
'package_name' => 'geo',
'package_path' => dirname( __FILE__ ).'/',
'service' => LIBERTY_SERVICE_GEO,
);
$gBitSystem->registerPackage( $registerHash );

if( $gBitSystem->isPackageActive( 'geo' ) ) {
require_once( GEO_PKG_PATH.'LibertyGeo.php' );

$gLibertySystem->registerService( LIBERTY_SERVICE_GEO, GEO_PKG_NAME, array(
'content_load_sql_function' => 'geo_content_load_sql',
'content_list_sql_function' => 'geo_content_list_sql',
'content_store_function' => 'geo_content_store',
'content_expunge_function' => 'geo_content_expunge',
) );
}
?>
{/code}

Every package has a bit_setup_inc.php file, and it is used by bitweaver to get key information about installed packages every time a page on a bitweaver site is requested. So in the first part of the file we tell bitweaver what package this is and since we are creating a service we also tell it what Liberty Service GUID it is associated with:

{code}
//set some basic package information
$registerHash = array(
'package_name' => 'geo',
'package_path' => dirname( __FILE__ ).'/',
'service' => LIBERTY_SERVICE_GEO,
);
//tell the bitweaver kernel about it
$gBitSystem->registerPackage( $registerHash );
{/code}

Then we declare which functions in our package to associate with which Liberty Service methods we want to connect to:

{code}
if( $gBitSystem->isPackageActive( 'geo' ) ) {
//we include our package class so bitweaver can access it
require_once( GEO_PKG_PATH.'LibertyGeo.php' );

//here we register our service functions
$gLibertySystem->registerService( LIBERTY_SERVICE_GEO, GEO_PKG_NAME, array(
'content_load_sql_function' => 'geo_content_load_sql',
'content_list_sql_function' => 'geo_content_list_sql',
'content_store_function' => 'geo_content_store',
'content_expunge_function' => 'geo_content_expunge',
) );
}
{/code}

There are several service options available to use. There are methods for loading data, for storing data, for deleting data, for displaying templates on different page types, etc. A list of available services you can connect to is also on the ((LibertyServices)) page.

Here the geo package hooks into four of these Liberty Services:
*__content_load_sql_function__
+When ever any content is loaded, and that could be wiki content, articles content, whatever, Liberty will automatigally also include whatever we specify in in our function: __geo_content_load_sql__
*__content_list_sql_function__
+ Likewise, when ever any list of content is loaded, Liberty will automatically also include whatever we specify in in our function: __geo_content_list_sql__
*__content_store_function__
+When ever any content is stored, __geo_content_store__ will also be called.
*__content_expunge_function__
+When ever content is stored, __geo_content_expunge__ will also be called.

The associated functions, such as geo_content_list_sql, we will define in our package class file. We will do that in just a minute.

Geo Package does not register any templates with Liberty. But it could. Here are some template registration possibilities taken from the StarsPackage, which is used for rating content, and how those work:

{code}
'content_body_tpl' => 'bitpackage:stars/stars_inline_service.tpl',
'content_list_tpl' => 'bitpackage:stars/stars_list_service.tpl',
{/code}

*__content_body_tpl__
+inserts the associated tpl above the requested content, for example above the text of a wiki page or an article.
*__content_list_tpl__
+inserts the associated tpl above every content item in a list, such as a list of wiki pages.

The associated templates would be saved in the __templates__ directory we created earlier.

!!Creating Your Service Class
!!!Create The Class In Our Class File
All package classes are built on the bitweaver kernel class. To do so we include a base class file. There are a few to choose from. For more complex packages it is good to use LibertyAttachable.php as a foundation, for a simple service like Geo Package all we need is BitBase.php. For which ever base file we want to use, we include it using the appropriate package path constant. In this case BitBase comes from the Kernel Package:

{code}
require_once( KERNEL_PKG_PATH.'BitBase.php' );
{/code}

Now that we have a foundation we can declare our package class. The class file and class name are the same. So in Geo Package the class name is LibertyGeo. Here is how we set it up:

{code}
class LibertyGeo extends LibertyBase {
var $mContentId;

function LibertyGeo( $pContentId=NULL ) {
LibertyBase::LibertyBase();
$this->mContentId = $pContentId;
}
}
{/code}

!!!Add Class Methods
There are several standard methods, found in almost all packages, you will probably want to add to your class. These methods are:
*load()
*store()
*verify()
*isValid()
*expunge()

These do fundamental stuff like get data, store changes, or delete data. The details of how they work are beyond the scope of this tutorial. These are not our service functions, but some of our service functions are dependant on them, so here is what they look like in the Geo Package for easy reference and easy copying:

{code}
class LibertyGeo extends LibertyBase {
var $mContentId;

function LibertyGeo( $pContentId=NULL ) {
LibertyBase::LibertyBase();
$this->mContentId = $pContentId;
}

/**
* Load the data from the database
* @param pParamHash be sure to pass by reference in case we need to make modifcations to the hash
**/
function load() {
if( $this->isValid() ) {
$query = "SELECT * FROM `".BIT_DB_PREFIX."geo` WHERE `content_id`=?";
$this->mInfo = $this->mDb->getRow( $query, array( $this->mContentId ) );
}
return( count( $this->mInfo ) );
}

/**
* @param array pParams hash of values that will be used to store the page
* @return bool TRUE on success, FALSE if store could not occur. If FALSE, $this->mErrors will have reason why
* @access public
**/
function store( &$pParamHash ) {
if( $this->verify( $pParamHash ) ) {
$table = BIT_DB_PREFIX."geo";
$this->mDb->StartTrans();
if( !empty( $this->mInfo ) ) {
$result = $this->mDb->associateUpdate( $table, $pParamHash['geo_store'], array( "content_id" => $this->mContentId ) );
} else {
$result = $this->mDb->associateInsert( $table, $pParamHash['geo_store'] );
}
$this->mDb->CompleteTrans();
$this->load();
}
return( count( $this->mErrors )== 0 );
}

/**
* Make sure the data is safe to store
* @param array pParams reference to hash of values that will be used to store the page, they will be modified where necessary
* @return bool TRUE on success, FALSE if verify failed. If FALSE, $this->mErrors will have reason why
* @access private
**/
function verify( &$pParamHash ) {
$pParamHash['geo_store'] = array();
if( $this->isValid() ) {
$this->load();
$pParamHash['geo_store']['content_id'] = $this->mContentId;
if(!empty( $pParamHash['geo'])){
if( isset( $pParamHash['geo']['lat'] ) && is_numeric( $pParamHash['geo']['lat'] ) ) {
$pParamHash['geo_store']['lat'] = $pParamHash['geo']['lat'];
}
if( isset( $pParamHash['geo']['lng'] ) && is_numeric( $pParamHash['geo']['lng'] ) ) {
$pParamHash['geo_store']['lng'] = $pParamHash['geo']['lng'];
}
if( isset( $pParamHash['geo']['amsl'] ) && is_numeric( $pParamHash['geo']['amsl'] ) ) {
$pParamHash['geo_store']['amsl'] = $pParamHash['geo']['amsl'];
}
if( !empty( $pParamHash['geo']['amsl_unit'] ) ) {
$pParamHash['geo_store']['amsl_unit'] = $pParamHash['geo']['amsl_unit'];
}
}
}
return( count( $this->mErrors )== 0 );
}

/**
* check if the mContentId is set and valid
*/
function isValid() {
return( @BitBase::verifyId( $this->mContentId ) );
}

/**
* This function removes a geo entry
**/
function expunge() {
$ret = FALSE;
if( $this->isValid() ) {
$query = "DELETE FROM `".BIT_DB_PREFIX."geo` WHERE `content_id` = ?";
$result = $this->mDb->query( $query, array( $this->mContentId ) );
}
return $ret;
}
}
{/code}

!!!Add Service Functions
Our service functions do not go in our class, they stand alone. For each function we registered with Liberty Services, we define it here.

Recall that in our Geo example we registered four functions:
*geo_content_load_sql
*geo_content_list_sql
*geo_content_store
*geo_content_expunge

The first two, the sql functions, actually inject sql into the load() methods of other tables. We explicitly specify each data column we want returned and typically use a LEFT JOIN to attach the service sql. load_sql and list_sql are in many cases identical or nearly identical.

Here is what they look like iin the Geo Package:
{code}
function geo_content_load_sql() {
global $gBitSystem;
$ret = array();
$ret['select_sql'] = " , geo.`lat`, geo.`lng`, geo.`amsl`, geo.`amsl_unit`";
$ret['join_sql'] = " LEFT JOIN `".BIT_DB_PREFIX."geo` geo ON ( lc.`content_id`=geo.`content_id` )";
return $ret;
}

function geo_content_list_sql() {
global $gBitSystem;
$ret = array();
$ret['select_sql'] = " , geo.`lat`, geo.`lng`, geo.`amsl`, geo.`amsl_unit`";
$ret['join_sql'] = " LEFT JOIN `".BIT_DB_PREFIX."geo` geo ON ( lc.`content_id`=geo.`content_id` )";
return $ret;
}
{/code}

Our store and expunge (fancy word for delete) are slighly different, they actually receive a hash from the package using the service, and invoke the service class methods. This is very clear in the expunge example here. The store example is a little less clear, we check to see if it doesn't execute properly, and if there are errors we capture them. Look for this line in the geo_content_store function:

{code}
if ( !$geo->store( $pParamHash ) ) {
{/code}

Thats the Geo class method store() being called. Here are the complete Geo store and expunge service functions:

{code}
function geo_content_store( &$pObject, &$pParamHash ) {
global $gBitSystem;
$errors = NULL;
// If a content access system is active, let's call it
if( $gBitSystem->isPackageActive( 'geo' ) ) {
$geo = new LibertyGeo( $pObject->mContentId );
if ( !$geo->store( $pParamHash ) ) {
$errors=array('geo'=> $geo->mErrors['geo']);
}
}
return( $errors );
}

function geo_content_expunge( &$pObject ) {
$geo = new LibertyGeo( $pObject->mContentId );
$geo->expunge();
}
{/code}

__And we are done!__

With these four functions in place, you can install your package and Liberty Services will automagically call these functions at the appropriate times. This is all you have to do to create a Liberty Service. It is quite easy! Remember, we didn't register or create any templates in this example, but if you want to you can. Just remember to add template files you register to your template directory. You will use smarty just the same as you would for any other template. For examples see the templates in the StarsPackage or PigeonholesPackage.

----
!Adding Liberty Services to an Existing Package
{maketoc}
!! Register The Package As Service
to turn any package into a service, you need to modify your <pkg>/bit_setup_inc.php file and declare your package into a service. this can be done by modifying the way the package is registered:
{code}
$gBitSystem->registerPackage( 'pigeonholes', dirname( __FILE__).'/', TRUE, LIBERTY_SERVICE_CATEGORIZATION );
{/code}
explaining the parameters of registerPackage:
# name of the package
# package path
# specify if the package can be activated
# specify what type of service the package belongs to

the service type needs to be defined as a constant in liberty/LibertySystem.php - this is also the place where you can check what types of services we already have and if your's fits into one of the existing ones, please use one of those.

!! Register The Services
This is the cruicial bit. we register the service and when to display what and where.

{code source=PHP}
if( $gBitSystem->isPackageActive( 'pigeonholes' ) ) {
$gLibertySystem->registerService( LIBERTY_SERVICE_CATEGORIZATION, PIGEONHOLES_PKG_NAME, array(
'content_display_function' => 'pigeonholes_content_display',
'content_preview_function' => 'pigeonholes_content_preview',
'content_edit_function' => 'pigeonholes_content_input',
'content_store_function' => 'pigeonholes_content_store',
'content_expunge_function' => 'pigeonholes_content_expunge',
'content_edit_tab_tpl' => 'bitpackage:pigeonholes/pigeonholes_input_inc.tpl',
'content_view_tpl' => 'bitpackage:pigeonholes/display_members.tpl',
'content_nav_tpl' => 'bitpackage:pigeonholes/display_paths.tpl',
) );
{/code}

You can see that the pigeonholes package first checks to see if it's active, if so, it adds the service specifications.

!!Create Your Service Functions and tpls
For each function and tpl you register in bit_setup_inc.php you need create. Templates you will create and save in your template folder. Your service functions get defined in your class file, in Pigeonholes this is the Pigeonholes.php. But note that these functions are not IN the class, they reside outside. There are a variety of functions you can register, some add sql and some call on your package methods. Here is one example which extends all content getList methods:

{code source=PHP}
function pigeonholes_content_list_sql ( &$pObject, $pParamHash = '') {
global $gBitSystem;
$ret = array();
if( /*$gBitSystem->isFeatureActive( 'pigeonholes_categorize_'.$pObject->getContentType() ) && */ !empty( $pParamHash['pigeonholes'] ) ) {
if ( is_array( $pParamHash['pigeonholes'] ) ) {
$ret['join_sql'] = "LEFT JOIN `".BIT_DB_PREFIX."pigeonhole_members` pm ON (lc .`content_id`= pm.`content_id`)";
$ret['where_sql'] = ' AND pm.`parent_id` in ('.implode( ',', array_fill(0, count( $pParamHash['pigeonholes'] ), '?' ) ).')';
$ret['bind_vars'] = $pParamHash['pigeonholes'];
} else {
$ret['join_sql'] = "LEFT JOIN `".BIT_DB_PREFIX."pigeonhole_members` pm ON (lc .`content_id`= pm.`content_id`)";
$ret['where_sql'] = " AND pm.`parent_id`=? ";
$ret['bind_vars'][] = $pParamHash['pigeonholes'];
}
}
return $ret;
}
{/code}

Once you have defined all your functions you are done. Liberty Services automagically does the rest.
Page History
Date/CommentUserIPVersion
28 Aug 2006 (17:22 UTC)
adds image
Will68.174.111.477
Current • Source
Will68.174.111.474
View • Compare • Difference • Source
xing194.152.164.452
View • Compare • Difference • Source
xing194.152.164.451
View • Compare • Difference • Source