Ransford Okpoti's Blog

March 18, 2012

Creating Dynamic Log Files In Zend Framework (ZF)

Filed under: PHP — Tags: , — ranskills @ 11:58 am

The motivation for wanting to do something like this could be any of the following:

  • You want to have separate logs depending on the context (i.e. application, testing, development, etc) in which the application is being run and you do not want to inherit a context and override some values in another context in the configuration file.
  • If your application builds huge log files and, as a result, you want to partition the logs into, say, daily, weekly, monthly, yearly, etc. logs

Below is a typical configuration in the application.ini file.

[application]
; Loggers
resources.log.stream.writerName             = "Stream"
resources.log.stream.writerParams.stream    = APPLICATION_PATH "/../data/logs/application.log"
resources.log.stream.writerParams.mode      = "a"
resources.log.stream.filterName             = "Priority"
resources.log.stream.filterParams.priority  = 4

The problem can easily be overcome by adding the highlighted lines to the configuration file, these settings will be used in the bootstrap file to create the log files.

; Loggers
resources.log.stream.writerName             = "Stream"
resources.log.stream.writerParams.stream    = APPLICATION_PATH "/../data/logs/application"
resources.log.stream.writerParams.mode      = "a"
resources.log.stream.filterName             = "Priority"
resources.log.stream.filterParams.priority  = 4

log.path               = APPLICATION_PATH "/../data/logs"
log.partitionStrategy  = "context"
log.partitionFrequency = "weekly"

New Entries Explained

log.path
Specifies the location/directory where the files would be created. NOTE: Linux users have to make sure the appropriate read/write permissions are set.
log.partitionStrategy
Defaults to context which denotes that log files will be prefixed with the context, or application environment, as is know in ZF, (e.g., production, development, testing , etc.) in which the application is being run.
log.partitionFrequency
Specifies when new log files get created and take on any of the following values: daily, weekly, monthly and yearly.

Next, in the Bootstrap.php, add _initLog()function as show below to dynamically create the log files based on the customized settings we provided in the application.ini.
The function simply alters the file name for the Stream logger by appending a unique string to the selected base file name based on the log.partitionFrequency value.

class Bootstrap extends Zend_Application_Bootstrap_Bootstrap {

    protected function _initLog() {
        $options = $this->getOption('resources');

        $partitionConfig = $this->getOption('log');
        $logOptions = $options['log'];

        $baseFilename = $logOptions['stream']['writerParams']['stream'];
        if ($partitionConfig['partitionStrategy'] == 'context'){
            $baseFilename = $partitionConfig['path'].'/'.APPLICATION_ENV;
        }

        $logFilename = '';
        switch(strtolower($partitionConfig['partitionFrequency'])){
            case 'daily':
                $logFilename = $baseFilename.'_'.date('Y_m_d');
                break;

            case 'weekly':
                $logFilename = $baseFilename.'_'.date('Y_W');
                break;

            case 'monthly':
                $logFilename = $baseFilename.'_'.date('Y_m');
                break;

            case 'yearly':
                $logFilename = $baseFilename.'_'.date('Y');
                break;

            default:
                $logFilename = $baseFilename;

        }

        $logOptions['stream']['writerParams']['stream'] = $logFilename;

        $logger = Zend_Log::factory($logOptions);
        Zend_Registry::set('logger', $logger);

        return $logger;
    }
}

February 21, 2012

Talk: Web Scraping, An Important Technique For Data Extraction

Filed under: Uncategorized — Tags: , — ranskills @ 7:14 pm

After the successful launch of Java 7, late last year, here in Ghana by Coders4Africa where I was the main speaker highlighting on the new language changes in Java, I am pleased to announce that I will be making another presentation on Web Scraping at the upcoming meeting.

Topic

Web Scraping, An Important Technique For Data Extraction

Introduces Web Scraping using PHP as a valuable alternative to extract data from other websites in the absence of APIs in Africa. Demonstrating how to fetch related data and producing outputs in standard formats such as json, xml, csv, etc.

Event Details

Coders4Africa In Action in Accra Ghana – March 3rd 2012 at BusyInetnet
Saturday, March 3, 2012 from 3:00 PM to 6:00 PM (GMT)
Accra, Ghana

You can register here.
A tutorial will be posted after the event on this blog.

February 5, 2012

Dropping All Tables In A MySQL Database Using A Stored Procedure

Filed under: MySQL — ranskills @ 1:33 pm

Using an engine like InnoDB, which enforces referential integrity through the use of foreign keys, poses a little problem when deleting a table whose field(s) act as foreign keys in other table(s). Dropping an entire database may seem like a convenient approach to use, but will not be an option to users who do not have database creation privileges on the database server, and for those who would want to preserve other objects in the database like views, functions, stored prodecures, etc.

Deleting interconnected tables in a database can be a little frustrating, but the solution is that simple, suppress foreign key checks, if necessary, and delete the tables of interest.
Implementing a solution in the database through the use of a stored procedure is show below.

DROP PROCEDURE IF EXISTS procDropAllTables $$

CREATE PROCEDURE procDropAllTables()

BEGIN
		DECLARE table_name VARCHAR(255);
		DECLARE end_of_tables INT DEFAULT 0;

		DECLARE cur CURSOR FOR 
			SELECT t.table_name 
			FROM information_schema.tables t 
			WHERE t.table_schema = DATABASE() AND t.table_type='BASE TABLE';
		DECLARE CONTINUE HANDLER FOR NOT FOUND SET end_of_tables = 1;

		SET FOREIGN_KEY_CHECKS = 0;
		OPEN cur;

		tables_loop: LOOP
			FETCH cur INTO table_name;

			IF end_of_tables = 1 THEN
				LEAVE tables_loop;
			END IF;

			SET @s = CONCAT('DROP TABLE IF EXISTS ' , table_name);
			PREPARE stmt FROM @s;
			EXECUTE stmt;

		END LOOP;
	
		CLOSE cur;
		SET FOREIGN_KEY_CHECKS = 1;
	END

Now, let’s drop all the tables by calling our stored procedure as show below.

CALL procDropAllTables();

The above procedure can be tweaked if table deletion is not desired, but should be rather emptied by changing the highlighted line to

SET @s = CONCAT('DELETE FROM ' , table_name);

This is only one of the approaches that could be used to solve the problem, but they all basically work on the same premise that foreign key checks MUST BE disabled.

October 4, 2011

How To Solve MySQL Character Sets and Collations Mixing Problems

Filed under: MySQL — Tags: , — ranskills @ 11:20 am

Character sets and collations enable MySQL to perform comparisons on strings, so if have two or more columns with different character sets and/or collations are used in a way that performs some comparison of some sort on them (either using =, >, <, , a JOIN ON, etc), MySQL will be a little confused and display the error message:

[Err] 1267 – Illegal mix of collations (latin1_danish_ci,IMPLICIT) and (latin1_swedish_ci,IMPLICIT) for operation ‘=’

Unfortunately, there is no single command at the database level that could convert the character sets and collations of all tables within a database. In fact, the only command which appears to get the job done only changes the default values of the character set and/or collation of a database, and does not affect existing tables, but only has effect on new tables to be created.

ALTER DATABASE {database_name} CHARACTER SET {character_set} COLLATE {collation};

Likely for us, there is just a command/syntax for changing these values at the table level, syntax shown below.

ALTER TABLE {table_name} CONVERT TO CHARACTER SET {character_set} COLLATE {collation};

Example:

ALTER TABLE employees CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci;

This problem can be resolved by ensuring that all the tables and their corresponding columns have the same character set and collation. If multiple databases are employed, then they should all have the same character set and collation.
Using the above commands would be a very boring and painful process if you have, say, anything above 20 tables in your database. You will realize that is no fun at all, so we will develop a stored procedure that will automatically iterate through all the tables in the database and sets the character set and collations to our desired values.

The codes for the stored procedure is show below, I presume you can create this in MySQL (If not, jump to the screencast below to see it).

DROP PROCEDURE IF EXISTS procChangeCharSetandCollationForAllTables $$

CREATE PROCEDURE procChangeCharSetandCollationForAllTables()

	BEGIN

		DECLARE table_name VARCHAR(255);
		DECLARE end_of_tables INT DEFAULT 0;
		DECLARE num_tables INT DEFAULT 0;

		DECLARE cur CURSOR FOR 
			SELECT t.table_name 
			FROM information_schema.tables t 
			WHERE t.table_schema = DATABASE() AND t.table_type='BASE TABLE';

		DECLARE CONTINUE HANDLER FOR NOT FOUND SET end_of_tables = 1;

		OPEN cur;

		tables_loop: LOOP

			FETCH cur INTO table_name;
			
			IF end_of_tables = 1 THEN
				LEAVE tables_loop;
			END IF;

			SET num_tables = num_tables + 1;

			SET @s = CONCAT('ALTER TABLE ' , table_name , ' CONVERT TO CHARACTER SET latin1 COLLATE latin1_general_ci');

			PREPARE stmt FROM @s;
			EXECUTE stmt;
		END LOOP;

		CLOSE cur;
	END $$

Once you have the above stored procedure created in the database, just run it as shown below to have all the tables use the same character set and collation which in this case happens to be
CHARACTER SET = latin1
COLLATE = latin1_general_ci

CALL procChangeCharSetandCollationForAllTables();

If you prefer to use a different character set and collation, say utf8 and utf8_general_ci respectively, just change the codes in line 23 to

SET @s = CONCAT('ALTER TABLE ' , table_name , ' CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci ');

[SCREENCAST COMING SOON]

July 11, 2011

How To Create a Gherkin Syntax Highlighter In gedit

Filed under: Uncategorized — Tags: , , , , , — ranskills @ 8:29 am

So, I woke up today and did a little bit of surfing, like I always do, and came across Cucumber, a Behaviour-Driven Development (BDD) framework in Ruby¬†and I bought into the idea as it focuses on the behavioural aspects of the system by allowing you to describe and test the system’s behavior. Well, like a typical PHP junkie, I had to look for alternatives in PHP and luckily my search lead me to Behat, a BDD framework in PHP and inspired by Ruby’s Cucumber. Python’s users of Freshen may also find this informative since it uses the same syntax.

I decided to try out the examples only to realize that Gherkin, the Domain-Specific Language (DSL) used to describe the features to be tested in the features file, had no syntax highlighting in gedit, the official text editor of the GNOME desktop environment.

These are steps required to provide a basic gherkin syntax highlighting.

Steps

  1. Locate the directory where all the language files used for source code highlighting are kept, i.e. the language-specs directory.

      Linux

    1. Type the command below to help us locate the language-specs directory.
    2.  locate gtksourceview | grep 'javascript.lang$' 

      The outcome of the above command is shown below with /usr/share/gtksourceview-2.0/language-specs/ as the directory of interest to us.

      /usr/share/gtksourceview-2.0/language-specs/javascript.lang 

      Windows

      The location is going to be either one of these, provided gedit was installed on drive C:\.

    1. C:\Program Files (x86)\gedit\share\gtksourceview-2.0\language-specs (if on Windows 7)
    2. OR

    3. C:\Program Files\gedit\share\gtksourceview-2.0\language-specs
  2. Create a gherkin.lang file into the language-specs directory with the content below.
  3. <?xml version="1.0" encoding="UTF-8"?>
    <!-- Author: 2011 Ransford Okpoti -->
    <language id="gherkin" _name="Gherkin" version="2.0" _section="Scripts">
          <metadata>
                <property name="mimetypes">text/x-feature</property>
                <property name="globs">*.feature</property>
          </metadata>
          <styles>
                <style id="keyword" 	_name="Keyword"		map-to="def:keyword"/>
                <style id="feature" 	_name="Feature"		map-to="def:type"/>
                <style id="steps_keywords" 	_name="Steps Keywords"	map-to="def:keyword"/>
                <style id="constructors" 	_name="Constructors"	map-to="def:type"/>
                <style id="variables" 	_name="Variables"	map-to="def:comment"/>
                <style id="comments" 	_name="Comments"	map-to="def:comment"/>
          </styles>
          <definitions>
                <context id="gherkin" class="no-spell-check">
                      <include>
                            <!-- Keywords -->
                            <context id="steps_keywords" style-ref="steps_keywords">
                                  <keyword>Given</keyword>
                                  <keyword>When</keyword>
                                  <keyword>Then</keyword>
                                  <keyword>And</keyword>
                                  <keyword>But</keyword>
                            </context>
    
                            <context id="comments" style-ref="comments" end-at-line-end="true">
                                  <start>#</start>
                                  <end>\n</end>
                            </context>
    
                            <context id="feature" style-ref="feature">
                                  <keyword>Feature</keyword>
                            </context>
    
                            <context id="constructors" style-ref="constructors">
                                  <keyword>Scenario</keyword>
                                  <keyword>Scenarios</keyword>
                                  <keyword>Outline</keyword>
                                  <keyword>Background</keyword>
                            </context>
    
                            <context id="variables" style-ref="variables">
                                  <match>(&lt;)(\w+)(&gt;)</match>
                            </context>
    
                            <context id="arguments" end-at-line-end="true">
                                  <start>\|</start>
                                  <end>\n</end>
                                  <include>
                                        <context ref="def:decimal" />
                                        <context ref="def:float" />
                                        <context ref="def:string" />
                                        <context id="table_headings">
                                              <match>\w+</match>
                                        </context>
                                  </include>
                            </context>
                      </include>
                </context>
          </definitions>
    </language>
    
  4. We are done, close gedit, if you have it opened, and open a gherkin source file with the extension .feature to see the beauty of our work.
  5. NOTE: On line 3, the _section attribute was set to Scripts indicating that the Gherkin menu item can be located from the program’s menu navigation: View -> Highlight Mode -> Scripts -> Gherkin.

Below is a gherkin source file with no syntax highlighting in gedit.

Gherkin File With No Syntax Highlighting

After the completion of our little procedure, you can see how beautiful our gherkin source file appears in gedit.

Gherkin File With Syntax Highlighting

NOTE: The above procedure can be followed to create syntax highlighting for any source file.
For a thorough explanation of the structure of the xml file, follow the official guide.

June 24, 2011

A Practical Introduction to Client-side JavaScript Unit Testing Using QUnit

Filed under: JavaScript — Tags: , , , — ranskills @ 10:55 am

“Wow, I thought I had that working”, how often do you say that to yourself or hear a colleague ranting and raving about this. Well, things break all the time in the software development cycle and you will need to have a consistent, unobtrusive and quick approach to finding out if critical features of an application is malfunctioning.

Over the past few years, automated application testing (unit testing, etc) has been associated with the traditional server-side programming languages, server-side because am discussing this in the web development context, such as Java, PHP, Python, any of the .NET languages. These languages have enjoyed testing frameworks that have been used on enterprise projects by teams that know and recognize the importance of testing on such large-scale projects where the risks are high. Testing is a vital ingredient in any important project, be it large-scale or not. At least, it gives you the added confidence that the application still performs like it was designed to, and as a developer, it is a requisite skill for you to possess. It also serves as a quality measure and aids in detecting points of failures in the application.

Below are some testing frameworks for some server-side programming languages:

PHP

Java

.NET

For a more comprehensive list, check Wikipedia’s List of Unit Testing Frameworks.

JavaScript, the most popular scripting language on the internet, has not been left out in all of these, there are quite a number of unit testing frameworks namely:

In this demonstration, I will be using jQuery‘s unit testing framework known as QUnit.
NOTE: Unit Testing is not difficult, you test an actual result against an expected result and that is it, nothing magical or mythical about it. So, do not think it is for the Zoros, the Rambos, the James Bonds of programming? Definitely not!

Show Me Some Code, Please!

Ok, I will be using two different examples to demonstrate how unit testing is done, i hope the examples satisfy a broad range of readers. Before I get started, you need to set up the initial project folder structure by doing the following, if you want to code along:

  1. Download QUnit by clicking on this link.
  2. Uncompressing the saved file should show a similar directory structure below.
  3. QUnit Download Entries

  4. Setup a folder structure like the one below for the purposes of this demonstration.
    1. Create the project folder called js_unit_testing
    2. Inside the folder created above, create a test folder with the name test, this will contain the unit tests we will be writing and the html file(s) to run the tests
    3. From the uncompressed file, copy the qunit folder into the project folder js_unit_testing and the test/index.html file into the test folder created above in step 3.2
    4. Your project folder structure should looking like this.

      Project Directory Initial Structure

      Project Directory Initial Structure

Now, with the project structure out of the way, lets do some real coding.

Example 1

A trivial example meant to demonstrate unit testing of a function that sums up numbers. This is a good place to start since it does not involve any thinking at all, at least i presume we can all do simple summation, like 1 + 3 = 4, 0 + 0 = 0, 60 + 40 + 1 = 101, etc.
In addition, our function should be able to sum up any number of numbers passed to it and throw an exception if any of the arguments is not a number.

  1. Create num.js file in the project folder with the content below.
  2. /**
     * A vararg function that sums up numbers
     * @return {Number}
     * @throws {Error} If any of the arguments is not strictly a number
     */
    function sum() {
        var total = 0,
            num = 0,
            numArgs = arguments.length;
        
        if (numArgs === 0) { 
            throw new Error('Arguments expected');
        }
        
        for (var i = 0; i < numArgs; i++) {
            num = arguments[i];
            if (typeof (num) !== 'number') {
                throw new Error('Only numbers are allowed but found ', typeof (num));
            }
            
            total += num;
        }
        
        return total;
    }
    

    Now we have to test the sum function. Note: a good test should not only test for situation that always make the program work, but should be broad enough to test for conditions that could actually let the test result in failures to see how well the code deals with such scenarios. A good test should always lead to the detection of errors in a program so that if can be fixed.

  3. Inside the test folder, create test.js, all tests will go here, with the content below.
  4. test("testSumWithValidArguments", function() {
        equal(sum(0, 0), 0, '0+0 = 0');
        equal(sum(2, 2), 4, '2+2 = 4');
        equal(sum(10, -1), 9, '10 + -1 = 9');
        equal(sum(-10, -1), -11, '-10 + -1 = -11');
        equal(sum(2,1, 5, 6), 14, '2 + 1 + 5 + 6 = 14');
        equal(sum(2.3, 1.5), 3.8, '2.3 + 1.5 = 3.8');
        
        notEqual(sum(2, 1), 5, '2 + 1 <> 5');
    });
    
    
    test("testSumWithNoArguments", function() {
        raises(function(){ sum(); }, 'Exception thrown');
    });
    
    test("testSumWithWrongDataToResultInException", function() {
        raises(function(){sum(2,'Ghana')}, 'String rejected');
        raises(function(){sum(2,{num:3})}, 'Object rejected');
    });
    

    Now, lets see the magic at work by opening the index.html in the test folder in the browser. Below is the screenshot of the output.

    Sum Test Successful

    Now to the beauty of this whole process, that is a convenient way to detect defects in the application. Let’s say, a colleague somehow deleted the line that throws an exception when no arguments are passed to the sum() function. On running the test again, the test for that scenario, testSumWithNoArguments, will fail as captured below.

    Sum Test Failure

Example 2

This second demonstration closely models a real world programming scenario.

A government agency has tasked you to develop an application to enable them keep track of all quasi-government institutions/companies. They should be able to add any number of departments to a particular company and also keep track of a company’s employees to enable the prompt notification of those who are due for retirement.
Here are some constraints/features for the system:

  1. A department can only be associated with a company once.
  2. A person who does not meet the minimum age of employment for a company must not be employed.
  3. Employee ids must be automatically generated for those who have not manually been assigned ids.

UML Diagram
The simplified UML Class diagram above depicts some of attributes of the models identified for the scenario in this example and their relationships. The diagram was generated using yUML, an online tool for creating and publishing simple UML diagrams. You can use the DSL codes below to regenerate the UML Class diagrams above on the site.


[note: A simple UML diagram{bg:cornsilk}]
[Company|MIN_AGE_OF_EMPLOYMENT=20;name;departments|addDepartments();employ();getTotalMonthlySalary()],[Department|id;name;employees|getTotalMonthlySalary();equals()],[Employee|firstName;lastName;birthYear;salary|ageAge();getName();toString()]
[Company]1-1..*[Department]
[Department]1-1..*[Employee]

Cool, huh?

Lets get into action.

  1. Create a new folder named app in the main project directory.
  2. Create model.js file in app with the content below. This file contains our three domain objects namely: Company, Department and Employee
  3. /** @namespace All entities in the application are defined here */
    var model = model || {};
    
    /**
     * @class Creates a new Employee instance
     */
    model.Employee = function (data) {
        data = data || {};
    
        /**
         * The first name of the employee
         * @property {String} firstName
         */
        this.firstName = data.firstName || '';
    
        /**
         * The last name or surname of the employee
         * @property {String} lastName
         */
        this.lastName = data.lastName || '';
    
        /**
         * The birth year of the employee
         * @property {Integer} birthYear
         */
        this.birthYear = data.birthYear ||  new Date().getFullYear();
    
        /**
         * The monthly salary of the employee
         * @property {Number} salary
         */
        this.salary = data.salary || 0;
    
        /**
         * @return {Integer} The age of the employee
         */
        this.getAge   = function () {
            return new Date().getFullYear() - this.birthYear;
        };
    
        /** @return {String} The name of the employee */
        this.getName  = function () {
            return this.firstName + ' ' + this.lastName;
        };
    
        /** @return {String} A string representation of this object */
        this.toString = function () {
            return this.getName();
        };
    };
    
    /**
     * @class Creates a new Department instance
     *
     * @param {String} name The name of the department
     * @param {Integer} [id=AUTO_GENERATED]
     */
    model.Department = function (name, id) {
        /** 
    	 * The id of the department which is auto generated when set blank
    	 * @property {Integer} id
    	 */
        this.id = id || parseInt(Math.random() * 100, 10);
    
        /**
    	 * The name of the department
    	 * @property {String} name
    	 */
        this.name = name;
        
        /** The company the department belongs to */
        this.company = null;
    
        /** List of employees in the department */
        this.employees = [];
    
        /**
         * @param {model.Employee} employee The employee to join the department
         */
        this.addEmployee = function (employee) {
            this.employees.push(employee);
        };
    
        /**
         * @return {Double} Total monthly salary for the department
         */
        this.getTotalMonthlySalary = function () {
            var total = 0;
            this.employees.forEach(function (employee) {
                total += employee.salary;
            });
    
            return total;
        };
    
        /**
         *
         * @param {Department} obj
         * @return {Boolean} true if the objects are the same, false otherwise
         */
        this.equals = function (obj) {
            return this.id === obj.id;
        };
    };
    
    /**
     * @class Creates a new Company instance
     */
    model.Company = function (data) {
        data = data || {};
    
        /** The minimum age of employment */
        this.MIN_AGE_OF_EMPLOYMENT = data.MIN_AGE_OF_EMPLOYMENT || 20;
    
        /**
         * @property {String} name The name of the company
         */
        this.name = data.name;
    
        /** List of departments making up the company */
        this.departments = [];
        
        /**
         * Adds a new department to the company
         * @param {model.Department} department
         * @throws {exception.DepartmentExistException} Department already exist
         */
        this.addDepartment = function (department) {
    
            this.departments.forEach(function (item) {
                if (department.equals(item)) {
                    throw new exception.DepartmentExistException(department.name);
                }
            });
    
            department.company = this;
            this.departments.push(department);
        };
    
        /**
         *
         * @param {model.Employee} employee
         * @param {model.Department} department
         * @throws {exception.EmployeeException} Exception thrown if the person fails any of the required criteria for employment
         */
        this.employ = function (employee, department) {
            if (employee.getAge() < this.MIN_AGE_OF_EMPLOYMENT) {
                throw new exception.EmployeeException('Employee must be at least ' + this.MIN_AGE_OF_EMPLOYMENT + ' years');
            }
    
            department.addEmployee(employee);
        };
    
        /**
         * @return {Double} Total monthly salary for the company
         */
        this.getTotalMonthlySalary = function () {
            var total = 0;
            this.departments.forEach(function (department) {
                total += department.getTotalMonthlySalary();
            });
    
            return total;
        };
    };
    
    
    
  4. Next, lets throw in some exceptions like you would do on the server-side by creating exception.js file with the content below.
  5. /** @namespace All exceptions thrown in the application are defined here */
    var exception = exception || {};
    
    /** @class Exception */
    exception.DepartmentExistException = function (value) {
        this.value = value;
        this.name = 'exception.DepartmentExistException';
        this.message = ' department already exist';
    
        this.toString = function () {
            return this.value + this.message;
        };
    };
    
    /** @class Exception */
    exception.EmployeeException = function (message) {
        this.name = 'exception.EmployeeException';
        this.message = message;
    
        this.toString = function () {
            return this.value + this.message;
        };
    };
    
  6. Finally, lets write some unit tests by adding the codes below to our test.js.
  7. module("model", {
        company: new model.Company({name: 'Coders4Africa'}),
    
        infoSys: new model.Department('Information System', 1),
        hr: new model.Department('Human Resource'),
        internalAudit: new model.Department('Internal Audit'),
    
        setup: function () {
            ok(this.company instanceof model.Company, 'Created company is of type model.Company');
        }
    });
    
    test("Employee", function () {
        var empInstance = new model.Employee({firstName: 'Ransford', lastName: 'Okpoti', birthYear: 1990, salary: 2000});
    
        ok(empInstance instanceof model.Employee, 'Created employee is an instance of model.Employee class');
        equal(empInstance.getName(), 'Ransford Okpoti', 'getName() function passed');
        ok(empInstance.getAge() >= 0, 'age should never be negative');
    });
    
    test("Department", function () {
        ok(this.infoSys instanceof model.Department, 'Created object is an instance of model.Department class');
        equal(this.infoSys.getTotalMonthlySalary(), 0, 'Total monthly salary for a new department is 0');
        equal(this.infoSys.company, null, 'Company is null for a newly created department');
        ok(this.infoSys.equals(this.infoSys), 'A department should pass the equals() test on itself');
        ok(!this.infoSys.equals(this.hr), 'Two different departments must not be the same');
        ok(typeof (this.internalAudit.id) === 'number' && this.internalAudit.id > 0, 'Auto-generated id is a positive integer');
    });
    
    test("Company", function () {
        var that        = this,
            year        = new Date().getFullYear(), // current year
            leger       = new model.Employee({firstName: 'Leger', lastName: 'Djiba', birthYear: year - 30, salary: 930}),
            amadou      = new model.Employee({firstName: 'Amadou', lastName: 'Daffe', birthYear: year - 35, salary: 850}),
            khalil      = new model.Employee({firstName: 'Ibrahima', lastName: 'Ndiaye', birthYear: year - 40, salary: 68}),
            kwame       = new model.Employee({firstName: 'Kwame', lastName: 'Andah', birthYear: year - 30, salary: 51}),
            abderemane  = new model.Employee({firstName: 'Abderemane', lastName: 'Abdou', birthYear: year - 50, salary: 41}),
            ranskills   = new model.Employee({firstName: 'Ransford', lastName: 'Okpoti', birthYear: year - 22, salary: 40}),
            prince      = new model.Employee({firstName: 'Prince', lastName: 'Nyarko', birthYear: year - 42, salary: 33}),
            james       = new model.Employee({firstName: 'James', lastName: 'Gaglo', birthYear: year - 50, salary: 32}),
            rufin       = new model.Employee({firstName: 'Rufin', lastName: 'Slyvestre', birthYear: year - 53, salary: 31}),
            mohammed    = new model.Employee({firstName: 'Mohammed-Sani', lastName: 'Abdulai', birthYear: year - 15, salary: 27});
    
        equal(this.company.departments.length, 0, 'Departments should be empty for a newly created company');
        equal(this.company.getTotalMonthlySalary(), 0, 'Total monthly salary for a new company is 0');
    
        this.company.addDepartment(this.infoSys);
        this.company.addDepartment(this.hr);
        this.company.addDepartment(this.internalAudit);
    
    
        this.company.employ(leger, this.infoSys);
        this.company.employ(amadou, this.infoSys);
        this.company.employ(khalil, this.infoSys);
    
        this.company.employ(kwame, this.hr);
        this.company.employ(abderemane, this.hr);
        this.company.employ(ranskills, this.hr);
    
        this.company.employ(prince, this.internalAudit);
        this.company.employ(james, this.internalAudit);
        this.company.employ(rufin, this.internalAudit);
    
        raises(function () { that.company.employ(mohammed, that.internalAudit); }, function (e) { return e.name === 'exception.EmployeeException'; }, 'exception.EmployeeException thrown when employing an unqualified person');
    
        equal(this.company.departments.length, 3, 'Department list correctly updated');
        raises(function () { that.company.addDepartment(that.infoSys); }, function (e) { return e.name === 'exception.DepartmentExistException'; }, 'exception.DepartmentExistException thrown when adding an existing department');
        equal(this.company.departments.length, 3, 'Department list does not get incremented when adding existing department');
    
        equal(this.company.getTotalMonthlySalary(), 2076, 'Checking the total monthly salary of the company');
    });
    
  8. Below is the screenshot after refreshing the test page in the browser.
  9. All Tests Successful

    You can drilldown to find the details of a test by clicking on it. E.g., clicking on the Company test shows the details of all the 8 successful tests that were run.

    Company Test Details

Suggested Activities

The best way to learn is to get involved, so I have intentionally left out some features you could implement and test for, like:

  1. Finding all employees who are qualified to go on a mandatory pension based on a simple age criteria.
  2. Ensure the uniqueness of a department’s id generated by the program when no id is passed to the Department constructor
  3. etc

Conclusion

There is so much more to software testing and what I have demonstrated should give you an idea of how testing is done, I would advise you to find some good books and read. For those who want to take it further and make a career out of it, you can find out more from the organization responsible for certifying software testers the world over, International Software Testing Qualifications Board (ISTQB).

Download

Download all the source codes here. All the codes are JSLint complaint.

May 26, 2011

How to use PHP’s __autoload

Filed under: PHP — ranskills @ 4:05 pm

Classes used in this posting may refer to classes or interfaces where appropriate.

Ever wondered if there could be a more appropriate way to include dependent files into other PHP files other than using multiple include or require language constructs? Well, wonder no more, the __autoload magic function in PHP5 comes to the rescue. In simple words, this method/function gets invoked anytime the execution reaches a line containing a class or an interface for which the interpreter does not know of it’s existence either because it has not been defined in the same file or it’s been defined in a separate file ,but has not been included in the current executing script/file.

The __autoload function allows for the lazy loading of classes, the classes only gets loaded when they are needed.

Lets look at some examples. In the code below, the __autoload function will never be called because there is only one class which has been defined within the file itself, so the interpreter knows of its definition.

<?php
function __autoload($class){
    echo 'Loading Interface/Class with name '.$class;
}

class Address{
}
?>

With this second example, the PHP interpreter does not known the definition of the interface IAddress, so it calls the __autoload function passing it the name IAddress, but since the function does not do anything yet except output the string Loading Interface/Class with name IAddress , and raises the error:

Interface ‘IAddress’ not found in path/to/script

<?php
function __autoload($class){
    echo 'Loading Interface/Class with name '.$class;
}

class Address implements IAddress{

}
?>

If the definition of the interface IAddress was in a different file say IAddress.php and in the same directory as our current script, we would have had the following code below.

<?php
include_once 'IAddress.php';
class Address implements IAddress{

}
?>

Lets look at another script which requires several classes defined in the classes directory.

<?php
include_once 'classes/Address.php';
include_once 'classes/Person.php';
include_once 'classes/ShoppingCart.php';

$addr = new Address();
?>

From the above code, it should hit you that if your script needs several classes/interfaces, that have rightly been, defined in their own separate files, then you’ll have to manually include all of them, so you could end up with 15 or 20 or more lines of include_once/require_once in your code. This definitely doesn’t sound exciting, you could argue that you could add the classes directory to your include path using the set_include_path function, but it still doesn’t take away the fact that you’ll have to include the class files using any of the file inclusion statements.

Now, lets solve the simple problem above before moving to a more complex scenario.

To quickly solve this, all we do is to remove all the include/require lines from the above code and add the __autoload function.

<?php
function autoLoader($class_name){
    include_once('classes/'.$class.'.php');
}

spl_autoload_register('autoLoader');

$addr = new Address();
?>

[notice type=attention]
For this to work, the class name should lead us to easily predict the name of the file to be included i.e., the class name must be part of the file name. So, a class named Address stored in
address.php,
address.class.php,
address.inc.php or
class.address.php
is more helpful than storing it in some_unrelated_name.php .
[/notice]
This works perfectly now, but it won’t work in all scenarios because

1. All classes may not be in a single directory called classes, unless you decide to do it so.

2. All classes may not be named in this format: class_name.php eg. Address.php, Person.php, etc. What happens if there are mixed class naming formats like Address.class.php, class.Address.php, Address.inc.php or any other format that the developer(s) of the class used.

While we are looking at situations that might break our seemingly wonderful solution to the class file inclusion nightmare, lets look at a class name like Zend_Log, Log_display or Archive_Tar, you can quickly take a look at the class names in the PEAR package/directory in relation to the directory they are defined in. The naming convention adopted was to solve the problem of class name clashes. The naming convention is: [directory_]+class
where the + implies that there must be at least one directory before the class name.
Here, the naming gives information about the directory that contains the actual class eg. here are some class names and their implied locations

Zend_Log -> Zend/Log.php

Calendar_Month_Weekdays -> Calendar/Month/Weekdays.php

Log_display -> Log/display.php

To solve this, we define our own autoloader function to try and load the required classes/interfaces. The code is shown below. Lets name the script autoloader.php

<?php
/**
 *
 * @param string $className Class or Interface name automatically
 *              passed to this function by the PHP Interpreter
 */
function autoLoader($className){
    //Directories added here must be relative to the script going to use this file
    $directories = array(
      '',
      'classes/'
    );

    //Add your file naming formats here
    $fileNameFormats = array(
      '%s.php',
      '%s.class.php',
      'class.%s.php',
      '%s.inc.php'
    );

    // this is to take care of the PEAR style of naming classes
    $path = str_ireplace('_', '/', $className);
    if(@include_once $path.'.php'){
        return;
    }
    
    foreach($directories as $directory){
        foreach($fileNameFormats as $fileNameFormat){
            $path = $directory.sprintf($fileNameFormat, $className);
            if(file_exists($path)){
                include_once $path;
                return;
            }
        }
    }
}

spl_autoload_register('autoLoader');
?>

Now, all we have to do is to include autoloader.php into any of the scripts what will be making use of other classes/interfaces.

<?php
include_once 'autoloader.php';

$addr = new Address();
$shoppingCart = new ShoppingCart();
$person = new Person();

$person->setAddress($addr);
$person->setShoppingCart($shoppingCart);

?>

[notice type=download]
Download autoloader.php
[/notice]

How To Add A Custom Id Generation Strategy To Doctrine 2.1

Filed under: PHP — Tags: — ranskills @ 12:50 pm

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.

July 28, 2009

How to Convert A Number To Words In PHP

Filed under: PHP — ranskills @ 4:48 pm

There comes a time when you really, really want a library to get a function/task performed for you and the results of your search on the internet does not give you what you actually want. Not in this case, am sure when you google the topic you should get a couple of interesting information, but the point i want to establish is that, being a little inquisitive to find out what is under the hood will help you know how the small small pieces join together to fulfill your needs.
The purpose of this posting is to give an insight into the PHP language by looking at variable definition, some control structures, classes and some inbuilt functions.

Naming variables in PHP is not all that different from other programming languages like Java, C# and the likes, the only exception is that all variables must start with the dollar sign, $, as demonstrated in the examples below.
The data type of a variable is determined by what value it holds and as a result you are not allowed to specify a variable’s data type before using it so you won’t find something like this anywhere

int $age = 30;

This is an example of a valid PHP code snippet

<?php
// this is a comment
/*
this is a multi-line comment
*/

$email = <a href=<a href="mailto:'person@nowhere.com'">mailto:'person@nowhere.com'</a>>'person@nowhere.com'</a>; // a string variable
$year = 2010; // an integer variable

/* a reference variable provided the class Company
has already been defined */
$company = new Company();
?>

Syntactically, PHP’s control structures and class definitions are just like their counterparts in the other modern languages such as Java and C#.

<?php

/**
 * A demo of class definition.
 *
 * @package clients
 * @author ranskills <a href=<a href="mailto:person@nowhere.com">mailto:person@nowhere.com</a>>person@nowhere.com</a>
 */
class Company {

    public static $numOfInstances = 0; // Class variable
    private $name; // Instance variable

    public function __construct($name) {
        self::$numOfInstances++;
        $this->name = trim($name);
    }

    /**
     * Getter method for the name attribute
     * @return string the name of the company
     */
    public function getName() {
        return $this->name;
    }

}
?>

Another interesting thing, is the concept of associative arrays which allows you to use strings to index your arrays. eg.

<?php

$countries = array(
    // index => value
    'gh' => 'Ghana',
    'sa' => 'South Africa'
);
?>

Now, lets look at some codes to convert any integer to it’s equivalent in words in English.

<?php

// Yes, you can create your own exceptions in PHP
class ArrayIndexOutOfBoundsException extends Exception {

    function __construct($message, $code = 0) {
        parent::__construct($message, $code);
    }

}

class Integer {

    public function toText($amt) {
        if (is_numeric($amt)) {
            echo '' . number_format($amt, 0, '.', ',') . '';
            $sign = $amt > 0 ? '' : 'Negative ';
            return $sign . $this->toQuadrillions(abs($amt));
        } else {
            throw new Exception('Only numeric values are allowed.');
        }
    }

    private function toOnes($amt) {
        $words = array(
            0 => 'Zero',
            1 => 'One',
            2 => 'Two',
            3 => 'Three',
            4 => 'Four',
            5 => 'Five',
            6 => 'Six',
            7 => 'Seven',
            8 => 'Eight',
            9 => 'Nine'
        );

        if ($amt >= 0 && $amt < 10)
            return $words[$amt];
        else
            throw new ArrayIndexOutOfBoundsException('Array Index not defined');
    }

    private function toTens($amt) { // handles 10 - 99
        $firstDigit = intval($amt / 10);
        $remainder = $amt % 10;

        if ($firstDigit == 1) {
            $words = array(
                0 => 'Ten',
                1 => 'Eleven',
                2 => 'Twelve',
                3 => 'Thirteen',
                4 => 'Fourteen',
                5 => 'Fifteen',
                6 => 'Sixteen',
                7 => 'Seventeen',
                8 => 'Eighteen',
                9 => 'Nineteen'
            );

            return $words[$remainder];
        } else if ($firstDigit >= 2 && $firstDigit <= 9) {
            $words = array(
                2 => 'Twenty',
                3 => 'Thirty',
                4 => 'Fourty',
                5 => 'Fifty',
                6 => 'Sixty',
                7 => 'Seventy',
                8 => 'Eighty',
                9 => 'Ninety'
            );

            $rest = $remainder == 0 ? '' : $this->toOnes($remainder);
            return $words[$firstDigit] . ' ' . $rest;
        }
        else
            return $this->toOnes($amt);
    }

    private function toHundreds($amt) {
        $ones = intval($amt / 100);
        $remainder = $amt % 100;

        if ($ones >= 1 && $ones < 10) {
            $rest = $remainder == 0 ? '' : $this->toTens($remainder);
            return $this->toOnes($ones) . ' Hundred ' . $rest;
        }
        else
            return $this->toTens($amt);
    }

    private function toThousands($amt) {
        $hundreds = intval($amt / 1000);
        $remainder = $amt % 1000;

        if ($hundreds >= 1 && $hundreds < 1000) {
            $rest = $remainder == 0 ? '' : $this->toHundreds($remainder);
            return $this->toHundreds($hundreds) . ' Thousand ' . $rest;
        }
        else
            return $this->toHundreds($amt);
    }

    private function toMillions($amt) {
        $hundreds = intval($amt / pow(1000, 2));
        $remainder = $amt % pow(1000, 2);

        if ($hundreds >= 1 && $hundreds < 1000) {
            $rest = $remainder == 0 ? '' : $this->toThousands($remainder);
            return $this->toHundreds($hundreds) . ' Million ' . $rest;
        }
        else
            return $this->toThousands($amt);
    }

    private function toBillions($amt) {
        $hundreds = intval($amt / pow(1000, 3));
        /* Note:taking the modulos results in a negative value, but
          this seems to work pretty fine */

        $remainder = $amt - $hundreds * pow(1000, 3);

        if ($hundreds >= 1 && $hundreds < 1000) {
            $rest = $remainder == 0 ? '' : $this->toMillions($remainder);
            return $this->toHundreds($hundreds) . ' Billion ' . $rest;
        }
        else
            return $this->toMillions($amt);
    }

    private function toTrillions($amt) {
        $hundreds = intval($amt / pow(1000, 4));
        $remainder = $amt - $hundreds * pow(1000, 4);

        if ($hundreds >= 1 && $hundreds < 1000) {
            $rest = $remainder == 0 ? '' : $this->toBillions($remainder);
            return $this->toHundreds($hundreds) . ' Trillion ' . $rest;
        }
        else
            return $this->toBillions($amt);
    }

    private function toQuadrillions($amt) {
        $hundreds = intval($amt / pow(1000, 5));
        $remainder = $amt - $hundreds * pow(1000, 5);

        if ($hundreds >= 1 && $hundreds < 1000) {
            $rest = $remainder == 0 ? '' : $this->toTrillions($remainder);
            return $this->toHundreds($hundreds) . ' Quadrillion ' . $rest;
        }
        else
            return $this->toTrillions($amt);
    }

}

$obj = new Integer();

echo $obj->toText(5234567);
echo $obj->toText(-4191770001230128);
?>

This is the output of the above code when run

5,234,567
Five Million Two Hundred Thirty Four Thousand Five Hundred Sixty Seven

-4,191,770,001,230,128
Negative Four Quadrillion One Hundred Ninety One Trillion Seven Hundred Seventy Billion One Million Two Hundred Thirty Thousand One Hundred Twenty Eight

With this knowledge, you can attempt to write a program to convert an amount to words including the name of the currency.

February 1, 2008

Smarty Overview

Filed under: Smarty Templating — Tags: , — ranskills @ 5:30 pm

Smarty is one of the most popular PHP templating engines. There are many other templating engines, based on similar principles, such as

  • PHPTemplate
  • PHPTAL (TAL Template Attribute Language), phptal.motion-twin.com/
  • Savant, http://phpsavant.com/
  • FastTemplate
  • PHPlib

Irrespective of the templating engine that one uses, one of the design goals is the separation of business logic from the presentation logic. A point of caution, you can still use Smarty, the wrong way, without the clear separation of the business and presentation logic since Smarty does not enforce the separation of the 2 logics.

Business logic basically has to deal with how you solve the problem at hand, such as the computation of an employee’s monthly salary using the various parameters required in a typical payroll application.

Presentation logic, as the name suggests, deals with the logic pertaining to the presentation of data/content only. Examples of presentation logic are: iterating through an array of data to be displayed, picking the first X number of characters to display such as displaying a news or an article summary, alternating row colours of tables, etc.

Simply put, a templating engine replaces placeholders with values you specify.
In a real world web development project, there are going to be 2 main teams working on the project namely:
The developers/programmers who uses the full power of the programming language to achieve the requirements of the system such as accessing data from a database or a web service, validating and process the data, and persisting or presenting the processed data if required.
Designer or user interface (UI) team which builds the interface the users of the application would interact with using xHTML and it’s various controls or any appropriate UI technology suitable for the project at hand without affecting the developers codes.

SMARTY INSTALLATION

The WordPress Classic Theme. Create a free website or blog at WordPress.com.

Follow

Get every new post delivered to your Inbox.