Ransford Okpoti's Dev Notes

July 20, 2010

How To Add A Custom Id Generation Strategy To Doctrine 2.0

Filed under: PHP — Tags: , , , — admin @ 10:30 am

Object-Relational Mapping (ORM) has caught up with PHP. In case you have been living under a rock, here’s the list of the popular ORM solutions in PHP:

With the advent of Doctrine 2.0.0 (which at the time of writing this article is at Beta 2), which requires a minimum of PHP version 5.3, our domain objects (models) are now free from subclassing a mandatory superclass as imposed by its predecessors (previous versions before 2.0.0).
Below is a code snippet of a User model as would be implemented in earlier versions of Doctrine before version 2.0.0.

class User extends BaseUser
{
}

The 2.0 version is totally different in that it now resembles the popular ORM solution in Java, Hibernate.
Now your entity classes can be a plain old PHP object (POPO) with annotations providing persistence information on how to update the persistent store with the field values of an instance of the entity class. There is also the option of using YAML, XML, or plain PHP to provide persistence information to the persistence manager.

I will just stick to the topic of this post by not delving into the nitty-gritties of how to setup and use Doctrine because it can boast of one of the best documentations, and tutorials on how to get it up and running.

In this artcle, we will be looking at how to use a UUID or GUID, for the primary keys of our entities instead of the traditional auto incremented unsigned numeric values, to demonstrate how easy it is to add a custom id generation strategy to your ever popular Doctrine.

If you ever decide to use a Universally Unique Identifier (UUID) or Globally Unique Identifier (GUID) as the primary keys for tables in your database, for various reasons such as having an application used by various clients in various geographical locations in a disconnected environment where each client has its own copy of the database with the likelihood of the various databases been merged into a centralized database server in the near future, then this would be particularly helpful.

1. In order to stick to the adopted convention, id generation classes reside in the Doctrine\ORM\Id namespace or package or directory (or which ever jargon you prefer to use), we let our UUIDGenerator extend the AbstractIdGenerator abstract class.

Below is the AbstractIdGenerator abstract class provided by Doctrine to be extended by all id generation strategies.

namespace Doctrine\ORM\Id;

use Doctrine\ORM\EntityManager;

abstract class AbstractIdGenerator
{
    abstract public function generate(EntityManager $em, $entity);

    public function isPostInsertGenerator()
    {
        return false;
    }
}

Now, we have to provide an implementation for the generate abstract method, and let the isPostInsertGeneration return false since the id will be generated and assigned to the entity/model before insertion into the related table. Various implementations of the UUID algorithm can be found on the net, so if you decide to implement your own version or find a more reliable and appropriate one just feel free to plug it in. By the way, I’ve even forgot the source of the UUID implementation am using in this article, so please forgive me if i haven’t credited the author(s) of the code.

Create UUIDGenerator.php in Doctrine\ORM\Id with the code below.

/**
 * @author Ransford Okpoti
 */
namespace Doctrine\ORM\Id;

use Doctrine\ORM\EntityManager;

class UUIDGenerator extends AbstractIdGenerator {
    /**
     * Generates an ID for the given entity.
     *
     * @param object $entity
     * @return string
     * @override
     */
    public function generate(EntityManager $em, $entity) {
        return self::v4();
    }

    /**
     * @return boolean
     * @override
     */
    public function isPostInsertGenerator() {
        return false;
    }

    public static function v3($namespace, $name) {
        if(!self::is_valid($namespace)) return false;

        // Get hexadecimal components of namespace
        $nhex = str_replace(array('-','{','}'), '', $namespace);

        // Binary Value
        $nstr = '';

        // Convert Namespace UUID to bits
        for($i = 0; $i < strlen($nhex); $i+=2) {
            $nstr .= chr(hexdec($nhex[$i].$nhex[$i+1]));
        }

        // Calculate hash value
        $hash = md5($nstr . $name);

        return sprintf('%08s-%04s-%04x-%04x-%12s',

                // 32 bits for "time_low"
                substr($hash, 0, 8),

                // 16 bits for "time_mid"
                substr($hash, 8, 4),

                // 16 bits for "time_hi_and_version",
                // four most significant bits holds version number 3
                (hexdec(substr($hash, 12, 4)) & 0x0fff) | 0x3000,

                // 16 bits, 8 bits for "clk_seq_hi_res",
                // 8 bits for "clk_seq_low",
                // two most significant bits holds zero and one for variant DCE1.1
                (hexdec(substr($hash, 16, 4)) & 0x3fff) | 0x8000,

                // 48 bits for "node"
                substr($hash, 20, 12)
        );
    }

    public static function v4() {
        return sprintf('%04x%04x-%04x-%04x-%04x-%04x%04x%04x',

                // 32 bits for "time_low"
                mt_rand(0, 0xffff), mt_rand(0, 0xffff),

                // 16 bits for "time_mid"
                mt_rand(0, 0xffff),

                // 16 bits for "time_hi_and_version",
                // four most significant bits holds version number 4
                mt_rand(0, 0x0fff) | 0x4000,

                // 16 bits, 8 bits for "clk_seq_hi_res",
                // 8 bits for "clk_seq_low",
                // two most significant bits holds zero and one for variant DCE1.1
                mt_rand(0, 0x3fff) | 0x8000,

                // 48 bits for "node"
                mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff)
        );
    }

    public static function v5($namespace, $name) {
        if(!self::is_valid($namespace)) return false;

        // Get hexadecimal components of namespace
        $nhex = str_replace(array('-','{','}'), '', $namespace);

        // Binary Value
        $nstr = '';

        // Convert Namespace UUID to bits
        for($i = 0;
        $i < strlen($nhex);
        $i+=2) {
            $nstr .= chr(hexdec($nhex[$i].$nhex[$i+1]));
        }

        // Calculate hash value
        $hash = sha1($nstr . $name);

        return sprintf('%08s-%04s-%04x-%04x-%12s',

                // 32 bits for "time_low"
                substr($hash, 0, 8),

                // 16 bits for "time_mid"
                substr($hash, 8, 4),

                // 16 bits for "time_hi_and_version",
                // four most significant bits holds version number 5
                (hexdec(substr($hash, 12, 4)) & 0x0fff) | 0x5000,

                // 16 bits, 8 bits for "clk_seq_hi_res",
                // 8 bits for "clk_seq_low",
                // two most significant bits holds zero and one for variant DCE1.1
                (hexdec(substr($hash, 16, 4)) & 0x3fff) | 0x8000,

                // 48 bits for "node"
                substr($hash, 20, 12)
        );
    }

    public static function is_valid($uuid) {
        return preg_match('/^\{?[0-9a-f]{8}\-?[0-9a-f]{4}\-?[0-9a-f]{4}\-?'.
                '[0-9a-f]{4}\-?[0-9a-f]{12}\}?$/i', $uuid) === 1;
    }

}

2. Next, add GENERATOR_TYPE_UUID = 6, just ensure that the assigned number is unique within the other GENERATOR_TYPE constants, to Doctrine\ORM\Mapping\ClassMetadataInfo.php

const GENERATOR_TYPE_UUID = 6;

3. Finally, we need to add our UUID generation strategy to the list of provided generation strategies. Go to the _completeIdGeneratorMapping function in Doctrine\ORM\Mapping\ClassMetadataFactory.php, and add the code below to the switch condition just before the default keyword.

            case ClassMetadata::GENERATOR_TYPE_UUID:
                $class->setIdGenerator(new \Doctrine\ORM\Id\UUIDGenerator());
                break;

I guess you are expecting a step 4? But am sorry to disappoint you, that is all it takes to accomplish our task. We can now make use of UUIDs as demonstrated through the use of annotations below:

/**
 * @Entity
 */
class User{
    /**
     *
     * @Id
     * @Column(type="string", length=36)
     * @GeneratedValue(strategy="UUID")
     */
    private $id;
}

Need I remind you that the downside of this approach is that you'll have to repeat this process each time you decide to upgrade to a higher version, since Doctrine does not implicitly have UUIDs as one of its id generation strategies and there is no convenient way to programmatically register an id generation strategy at runtime. In my honest opinion, this 2 minutes step is well worth the effort.

Share and Enjoy:
  • Print
  • Digg
  • Sphinn
  • del.icio.us
  • Facebook
  • Mixx
  • Google Bookmarks
  • Twitter

October 2, 2009

Decorating A Form Using JavaScript

Filed under: Javascript — admin @ 12:44 pm

Javascript plays an important role in the development of any web application, irrespective of the server-side language employed, since it resides on the client’s browser and can interact with the Document Object Model (DOM) of the web browser. Over the last couple of years, we have seen the emergence of great javascript utility libraries providing various functionalities which takes away the nightmare of handling browser hacks when you decide to develop your own javascript libraries from scratch. Prototype, Script.aculo.us, jQuery, ExtJs, YUI, Dojo, and Mootools are some of the javascript libraries out there providing functionalities such as serving as general utility libraries, visual effects, drag and drop, and providing customizable user interface (UI) widgets which can be used to build entire UIs using only javascript.
Enough of the talking, now to the issue. A good form design must be able to communicate a reasonable amount of information to the user to enable him (no gender segregation intended) perform the task required , such as filling out a new entry, editing an existing entry, performing a search, etc, without any ambiguity whatsoever. Simply put, users need to be armed with all the instructions/information to enable them complete a form, such as

  • which fields are mandatory/required
  • which fields can be used to perform a quick search on the form
  • acceptable data formats for some fields such as date field, etc
  • any other useful instruction

in a short and concise manner.
Lets take a look at the form below where a user is required to complete with the following fields been required:

  • Name
  • Gender
  • Country
  • Postal Address
  • Preferable work countries

and the these fields can be used in a search:

  • Name
  • Gender
  • Country

Below is a screen shot of an undecorated form.

Undecorated Form

Undecorated Form

Despite the fact that this form may have these validations in place, it does not communicate that to the user. The user is only brought into the loop when something goes wrong (annoying, huh? Why do you wait for me to do the wrong thing before informing me?) and this is just not good enough.
Now, we will take that form and make it friendly for our unsatisfied users. There are several ways to achieve this task, but i think a better approach would be not to introduce any complexities into the form designing by sticking little images/icons beside controls that need to be decorated. It should be as painless as the introduction of intuitive custom attributes like:

  • required – marks fields that are required
  • searchable – marks fields that can be used as paratemers in a search operation, with the ability to click on the icon to perform a search directly
  • info – displays an information icon beside the control, and on a mouse move shows any additional information about the field

Sample usage (our custom attribute is displayed in green):

?View Code HTML4STRICT
<input type="text" id="email" required="true" />

Now, lets build our little javascript library by making use of the following libraries:

  • prototype
  • wz_tooltip, which creates easy-to-use cross-browser tooltips

We will first start by defining our function in a formUtil namespace which can be extended by adding other form related useful functionalities.

?View Code JAVASCRIPT
1
2
3
var formUtil = {
     version = '1.0'
}

The decorator namespace under the formUtil will contain all the codes related to the problem we want to solve.

?View Code JAVASCRIPT
4
formUtil.decorator = {}

The next lines of code are pretty straight forward, we define all the attributes that determines whether a form element needs decorating or not.

?View Code JAVASCRIPT
5
6
7
8
9
10
/**
 * Defines our custom attributes
 */
formUtil.decorator.attributes = [
    "required", "searchable", "info"
]

The icons/images displayed beside each decorating element is defined here, so point these to actual image paths on your server or any appropriate place.

?View Code JAVASCRIPT
/**
 * Images used to decorate the form.
 * These must point to images in the application.
 */
formUtil.decorator.img = {
    REQUIRED: 'img/required.gif',
    SEARCH: 'img/search.gif',
    INFO: 'img/info.png'
}

These are the templates which determines the markup for the various decorations. Template is a class from prototype.

?View Code JAVASCRIPT
formUtil.decorator.templates = {
    DEFAULT: new Template('<span id="#{id}"><img src="#{imgSrc}" style="vertical-align:top"></span>'),
    INFO : new Template('<span id="#{id}"><img alt="Info" src="#{imgSrc}" style="vertical-align:top" onmouseover="Tip(\'#{title}\',SHADOW, true,STICKY, 1, CLICKCLOSE, true,DELAY, 1000)" onmouseout="UnTip()"></span>'),
    LINK:  new Template('<span id="#{id}"><a href="javascript:#{fn}"><img src="#{imgSrc}" style="vertical-align:top;border:0"></a></span>')
}

Next, we will define the layout which will come in handy when determining which element to decorate in a group of related fields, such as radio buttons, check boxes, etc.

?View Code JAVASCRIPT
11
12
13
14
15
16
17
/**
 * Layout arrangment for grouped controls like checkboxes or radio buttons.
 */
formUtil.decorator.layout ={
    HORIZONTAL: 'horizontal',
    VERTICAL: 'vertical'
}

The default configuration for the decorate function is defined below. It provides a flexible and convenient way of turning off the decoration of any of the attributes defined above.

?View Code JAVASCRIPT
18
19
20
21
22
23
24
25
26
27
28
29
/**
 * Default configuration options which may be overriden by the user
 */
formUtil.decorator.cfgDefaults = {
    renderRequired: true,
    renderSearchable: true,
    renderInfo: true,
    searchable: {
        clickable: false,
        fn: 'void(0)' // function to be clicked when clickable is set to true
    }
}

The decorateForm function does all the dirty work by iterating through all the form elements and decorating elements having our custom defined attributes.

?View Code JAVASCRIPT
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
/**
 * @param
 */
formUtil.decorator.decorateForm = function(form, cfgOptions){
    // Iterating through all the elements/controls in each form
    for(var j=0, k=form.elements.length; j<k ; j++){
        var elem = form.elements[j];
 
        var isDecoratable = false;
        for(var i in this.attributes){
            if($(elem).readAttribute(this.attributes[i])!==null){
                isDecoratable = true;
                break;
            }
        }
 
        if(!isDecoratable) continue
 
        var uniqueId = ( elem.id || elem.name);
 
        // ids for the icons displayed beside each field
        for(i in this.attributes){
            attribute = this.attributes[i];
            if(typeof attribute=='string'){
                var str = 'var ' + attribute + 'Id="' + uniqueId + '_' + attribute+'";';
                eval(str);
            }
        }
 
        /**
         * The element besides which the various decorators
         * will be rendered. This will normally be the element with any of
         * the custom attributes, expect in grouped controls, such as checkboxes,
         * or radio buttons, where the rendering element may either be the first
         * or last control in the group depending on the layout arrangement.
         */
        var renderingEl = elem;
        var numOptions = document.getElementsByName(elem.name).length;
        var layout = $(elem).readAttribute("layout") || this.layout.HORIZONTAL;
        layout = layout.toLowerCase();
 
        if(numOptions>1){ // it's either a radio button or a checkbox
            // if the options are arranged horizontally, then the last option
            // in the list is used.
            if(layout==this.layout.HORIZONTAL)
                renderingEl = document.getElementsByName(elem.name)[numOptions-1];
            else
                renderingEl = elem;
            renderingEl=renderingEl.next();
        }
 
        if(cfg.renderInfo && $(elem).readAttribute("info")!==null){
            renderingEl.insert({after: formUtil.decorator.templates.INFO.evaluate({ id:infoId, imgSrc:this.img.INFO, title:$(elem).readAttribute("info") })});
            renderingEl=renderingEl.next();
        }
 
        if(cfg.renderRequired && $(elem).readAttribute("required")=="true"){
            renderingEl.insert({after: formUtil.decorator.templates.DEFAULT.evaluate({ id:requiredId, imgSrc:this.img.REQUIRED })});
            renderingEl=renderingEl.next();
        }
 
        if($(elem).readAttribute("searchable")=="true"){
            if(cfg.searchable.clickable){
                renderingEl.insert({after: formUtil.decorator.templates.LINK.evaluate({ id:searchableId, fn:cfg.searchable.fn, imgSrc:this.img.SEARCH })});
            }
            else{
                renderingEl.insert({after: formUtil.decorator.templates.DEFAULT.evaluate({ id:searchableId, imgSrc:this.img.SEARCH })});
            }
        }
    }
}

This is a convenient method to call since it iterates through all the forms in a document and calls the decorateForm passing it each iterated form for decoration.

?View Code JAVASCRIPT
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
/**
 * Decorates a forms elements having the custom attributes
 * required, searchable, and info.
 * @example
 * 1. formUtil.decorator.decorate(); // default config applies
 *
 * 2. formUtil.decorator.decorate({renderInfo: false}); // overrides renderInfo
 */
formUtil.decorator.decorate = function(cfgOptions){
    var userCfg = cfgOptions || {};
    cfg = apply(userCfg, this.cfgDefaults);
 
    // Iterating through all the forms
    for(var i=0,j=document.forms.length; i<j; i++){
        this.decorateForm(document.forms[i], cfg);
    }
}

It is alway important to ensure that functions which accepts objects are passed the right objects for the function to work as expected. The best way to do this in javascript is to define the expected object and applying it to the incoming object the function receives, this way missing properties from the incoming object can be appropriately be replaced with the one from the expected object type, which i term the default configuration. An implementation of such a method is shown below.

?View Code JAVASCRIPT
/**
 * Copies all the non-existent properties in o from defaults
 *
 * @param {Object} o
 * @param {Object} defaults
 * @return {Object}
 */
function apply(o, defaults){
    var no = new Object();
    for(var p in defaults){
        no[p] = o[p] || defaults[p];
    }
 
    return no;
}

Below is the modified html that produced the undecorated form show above with our new attributes and the inclusion of our little javascript file.


Notice the custom attributes have been displayed in green for easy recognition.


?View Code HTML4STRICT
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
    <head>
        <title>Javascript Form Decorator</title>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <link type="text/css" rel="stylesheet" href="style.css" >
    </head>
    <body>
        <div class="notice">
            Complete the form and hit the Submit button.<br/>
            You can perform a quick search
            by providing values for searchable fields and click on the Search button.
        </div>
        <form id="applicationForm">
            <fieldset>
                <legend>APPLICATION FORM</legend>
                <p>
                    <label for="name" class="label">Name</label>
                    <input type="text" id="name" info="Official name as appears on the document" required="true" searchable="true" />
                </p>
                <p>
                    <label for="" class="label">Country</label>
                    <input type="text" id="country" required="true" searchable="true"/>
                </p>
                <p>
                    <label for="gender" class="label">Gender</label>
                    <input type="radio" name="gender" id="male" value="M" required="true" searchable="true"/><label for="male">Male</label>
                    <input type="radio" name="gender" id="female" value="F" /><label for="female">Female</label>
                </p>
                <p>
                    <label for="salary" class="label">Salary</label>
                    <input type="text" id="salary" />
                </p>
                <p>
                    <label for="postalAddr" class="label">Postal Address</label>
                    <textarea id="postalAddr" required="true"></textarea>
                </p>
                <p>
                    <label for="preferableCountries" class="label">Choose preferable work countries</label>
                <fieldset>
                    <input type="checkbox" value="gh" name="preferableCountries" id="gh" required="true" layout="Vertical"/><label for="gh">Ghana</label><br/>
                    <input type="checkbox" value="tg" name="preferableCountries" id="tg" /><label for="gh">Togo</label><br/>
                    <input type="checkbox" value="ch" name="preferableCountries" id="ch" /><label for="ch">China</label>
                </fieldset>
                </p>
                <p>
                    <label class="label" ></label>
                    <input type="submit" value="Save" /><input type="submit" value="Search" />
                </p>
            </fieldset>
        </form>
 
        <script type="text/javascript" src="../js/prototype-1.6.0.3.js"></script>
        <script type="text/javascript" src="../js/wz_tooltip/wz_tooltip.js"></script>
        <script type="text/javascript" src="form-util.js"></script>
        <script type="text/javascript">
        /*<![CDATA[*/
            window.onload = function(){
                form.decorateForm({searchable:{clickable:true, fn:'doSearch()'}});
                //form.decorateForm();
            }
            function doSearch(){
                console.info('Performing search...');
            }
        /*]]>*/
        </script>
 
    </body>
</html>

Here is the accompanying Cascading Style Sheet (CSS) which formats our page.

/*style.css*/
body{
    font: caption;
    font-size: 12px;
}
 
div.notice {
    border:1px solid orange;
    background: yellow;
    margin-bottom:5px;
    padding:5px;
    font-family:monospace;
}
 
label.label {
    display: block;
    float: left;
    width: 220px;
    text-align: right;
    margin-right: 5px;
    padding: 2px 0;
}
 
fieldset {
    border: none;
    padding: 0;
    margin: 0;
}
 
legend {
    /*font-weight: bold;*/
    margin-bottom: 0px;
    padding: 0px;
}
 
form p{
    margin-top: 1px;
    margin-bottom: 5px;
}

The form below shows the results of the javascript form decorator in action.

Decorated Form

Decorated Form

This approach to the problem could be explored to build a validator for a form using javascript. If you are not such a big fan of adding custom attributes to XHTML elements, you could alter the decorate function by passing it the list of elements to be decorated instead of picking all the elements having our custom attributes on them.

Share and Enjoy:
  • Print
  • Digg
  • Sphinn
  • del.icio.us
  • Facebook
  • Mixx
  • Google Bookmarks
  • Twitter

August 24, 2009

Building Modifiers and Functions In Smarty

Filed under: PHP,Smarty — Tags: , , — admin @ 10:17 am

In this article, we will be looking at how to build modifiers and functions in Smarty, a very popular templating engine in PHP. If you don’t know how to setup Smarty up in your project, you can refer to my earlier post on Smarty Templating Overview which is accompanied by a screencast.

A quick look into the Smarty plugins directory should give you an idea of what’s under the hood, the obvious thing you would notice is the file naming convention adopted:
type.function_name.php
where type could be modifier, function, etc.
So, you will typically end up with file names of this structure:

  • modifier.[modifier name goes here].php
  • function.[function name goes here].php
  • etc

MODIFIERS

To be a little curious, lets open modifier.lower.php

<?php
// Comments removed for brevity
function smarty_modifier_lower($string)
{
    return strtolower($string);
}
?>

The above file only shows you that a Smarty plugin is just a normal PHP function, as you may be aware functions are first class citizens in PHP, which accepts arguments and returns a value that would be placed at the location it was called in the template file it self. So, {'Hmm, Interesting Discovery' | lower} in a template file would result in hmm, interesting discovery being substituted in the place of {'Hmm, Interesting Discovery' | lower} in the generated file from that template.
General usage:
{ argument_to_pass_to_the_modifier | modifier_name_goes_here}

The modifier always appears at the right, and there is a pipe | separating it and the argument it will work on.

Ok, so now what? Do you have a website with funny, and uninteresting URLs like
http://mysite.com/article.php?id=2,
where 2 may refer to an article with the title “All You Need To Know To Build Smarty Plugins”.
Let’s try and create a Search Engine Optimization or pretty URL for that article which would result in us having a new friendly URL like

http://mysite.com/article/all-you-need-to-know-to-build-smarty-plugins-2*

* To show the article, you will need to use apache mod_rewrite, but that is not the topic for discussion now, we just want to transform or slugify All You Need To Know To Build Smarty Plugins to all-you-need-to-know-to-build-smarty-plugins. And as a personal preference, i always append the unique id at the end of the generated URL so I do not run into problems when the title of the article is changed.

STEPS

Go into the plugins directory and create a file with the name modifier.seourl.php
Open the file and place the content below into it.

<?php
/**
 * Smarty seourl modifier plugin
 *
 * Type:     modifier<br>
 * Name:     seourl<br>
 * Purpose:  creates a Search Engine Optimized string
 *
 * @author   Ransford Okpoti <ranskills at yahoo dot co dot uk>
 * @param string
 * @return string
 */
function smarty_modifier_seourl($string) {
    $notAcceptableCharactersRegex = '#[^-a-zA-Z0-9_ ]#';
    $string = trim(preg_replace($notAcceptableCharactersRegex, '', strtolower($string)));
    return preg_replace('#[-_ ]+#', '-', $string);
}
 
?>

Once the file is saved, you can go to your template and use it like any normal modifier. E.g.
{$articleTitle | seourl}, {'Just what to see it working' | seourl}.
That’s it for modifiers, so just feel at home and create one if you see the need to.

FUNCTIONS

Smarty functions are similar in nature to their modifiers counterpart,but there is substantial difference in the way they are used within templates.

Usage:
{function_name optional_arguments}
where optional_arguments is in the form key1=value [, key2=value2]*

Lets create a very useful function which would enable us to dynamically include javascript files into our template.
This is extremely helpful if you want to stick to the generally accepted standard of adding javascript files to the tail end of your application or site to improve the performance of the site in terms of how long it takes the site to be loaded.


If you are interested in improving the performance of your site, you can follow the Best Practices for Speeding Up Your Web Site on yahoo which is an interesting and revealing information for any serious web developer.

A practical usage would be to include our little function we are about to develop in the main template of the site and to dynamically add the javascript files depending on which template(s) needs them.

Within the plugins directory, create a file named function.javascript.php with the content below.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
<?php 
/**
 * Smarty plugin
 * @package Smarty
 * @subpackage plugins
 */
 
/**
 * Smarty {javascript} function plugin
 *
 * Type:     function<br>
 * Name:     javascript<br>
 * Purpose:  outputs javascript tags for the provided srcs
 * @author Ransford Okpoti <ranskills at yahoo dot co dot uk>
 * @version 1.0
 * @example {javascript srcs='/script location'}
 *          {javascript srcs=$scripts}
 * @param array parameters
 * @return string
 */
function smarty_function_javascript($params) {
	$retval = '';
 
	if(isset($params['srcs'])){
		$scriptTemplate = '<script type="text/javascript" src="{0}"></script>';	
 
		$srcs = $params['srcs'];
	    if(is_array($srcs)){
	        foreach($srcs as $src){
	            $retval .= str_replace('{0}', $src, $scriptTemplate);
	        }
	    }
	    else{
	        $retval = str_replace('{0}', $params['srcs'], $scriptTemplate);
	    }			
	}
 
	return $retval;;
}
 
?>

Usage:
You could have this in your php file that is responsible for rendering the template.

...
// You could have some logic here to determine if a javascript file is required on a certain page
$scripts = array(
       '/public/js/contentslider.js',
       '/public/js/flash.js'
);
 
 
$this->smarty->assign('srcs', $scripts);
$this->smarty->display('template.xhtml');

The template showing just the line where our newly created Smarty function is used.

?View Code SMARTY
{javascript srcs=$srcs}

would result in this:

?View Code HTML4STRICT
<script type="text/javascript" src="/public/js/contentslider.js"></script>
<script type="text/javascript" src="/public/js/flash.js"></script>

This is just one approach of building Smarty plugins, the other approach is to use your existing classes or functions and register them using the function register_type*(…) on a Smarty instance/object.

*
Type could be any of the following:

  1. modifier
  2. function
  3. block
  4. outputfilter
  5. etc

Go! Go!! Go!!! Let Smarty work for you by developing your own plugins to provide extra functionalities.

[SCREEN CAST IS COMING SOON...]

Share and Enjoy:
  • Print
  • Digg
  • Sphinn
  • del.icio.us
  • Facebook
  • Mixx
  • Google Bookmarks
  • Twitter
Older Posts »

Powered by WordPress