History of CreatingServices

Differences from version 2 to 7



@@ -1,9 +1,347 @@

+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)).
 
-a brief outline of what a service is and what it does, can be found on LibertyServices
+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.
 
-! Turning a package into a service
-!! register the package as service
+!!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=393}
+
+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=394}
+
+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=395}
+
+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=396}
+
+!!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 );

@@ -16,8 +354,8 @@

 
 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 service
-this is the cruicial bit. we register the service and when to display what and where.
+!! 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' ) ) {

@@ -33,62 +371,29 @@

  ) );
 {/code}
 
-you can see that the pigeonholes package first checks to see if it's active, if so, it adds the service specifications.
+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:
 
-!! Service Options
-!!! Sql Extensions
-__content_list_sql_function__
-allows the extension of existing SQL while getList() is called. table aliases are kept constant throughout so you can easily join pertinent data to the existing SQL when getList() is called from any package.
 {code source=PHP}
-function protector_content_list() {
- global $gBitUser;
- $groups = array_keys($gBitUser->mGroups);
- $ret = array(
- 'join_sql' => " LEFT JOIN `".BIT_DB_PREFIX."liberty_content_group_map` lcgm ON ( lc.`content_id`=lcgm.`content_id` ) LEFT OUTER JOIN `".BIT_DB_PREFIX."users_groups_map` ugm ON ( ugm.`group_id`=lcgm.`group_id` ) ",
- 'where_sql' => " AND (lcgm.`content_id` IS NULL OR lcgm.`group_id` IN(". implode(',', array_fill(0, count($groups), '?')) ." ) OR ugm.`user_id`=?) ",
- 'bind_vars' => array( $gBitUser->mUserId ),
- );
- $ret['bind_vars'] = array_merge( $groups, $ret['bind_vars'] );
+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}
-You can also insert some select_sql as well, to limit the selection critieria.
-
-__content_load_sql_function__
-This is basically the same as content_list_sql_function but it's called during the load() process. the output should be virtually the same.
-
-!!! Functions
-__content_display_function__
-This function is called during the displaying of the content, i.e. while the page is being loaded. this means, you can add more sophisticated queries and checks in here, if there is no possibility to do this with the SQL functions above.
-
-__content_preview_function__
-This function is called when a page is being editied and a user hits preview. it's mostly there for you to make any selections the user has made persistent during the preview phase.
-
-__content_edit_function__
-When a user wants to edit content, this function is called. it allows you to generate data for any templates you need to fill in the edit page (more below).
-
-__content_store_function__
-Here you can store the content data in your serice tables.
-
-__content_expunge_function__
-Make sure the data from your tables is removed when a user removes the content it belongs to.
-
-!!! Templates
-__content_edit_tab_tpl__
-This template is displayed as a seperate tab when a user is editing content. this is useful for services that might contain several lines of code and want to have their own section when a user edits a page. This template is only available when the user is editing data.
-
-__content_edit_mini_tpl__
-This template is displayed during the edit process and is particularly useful when you have a single dropdown to display, since it's displayed below the textarea a user uses to edit the content. This template is only available when the user is editing data.
-
-__content_view_tpl__
-This template is appended to the bottom of the page, like a list of pages in the same category. This is only visible when the full content is loaded i.e.: when an article is being read but not just viewed on the front page.
-
-__content_nav_tpl__
-You can use this to insert a narrow template at the top of a page. this can be useful to display the path to an object or the status of a page. This is only visible when the full content is loaded i.e.: when an article is being read but not just viewed on the front page.
-
-__content_icon_tpl__
-insert an icon in the list of existing icons in the top right on a page. This will enable you to get your users to link to a template in your own package and deal with certain needs that way.
 
-__content_body_tpl__
-insert a small template into the top of the <div class="body"> part of the content. this template will be visible in listings such as the articles front page as well.
+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