Tuesday, September 23, 2008

Using ADOdb with PHP and Oracle: an advanced tutorial

1. Introduction

Oracle is the most popular commercial database used with PHP. There are many ways of accessing Oracle databases in PHP. These include:

  • The oracle extension
  • The oci8 extension
  • PEAR DB library
  • ADOdb library

The wide range of choices is confusing to someone just starting with Oracle and PHP. I will briefly summarize the differences, and show you the advantages of using ADOdb.

First we have the C extensions which provide low-level access to Oracle functionality. These C extensions are precompiled into PHP, or linked in dynamically when the web server starts up. Just in case you need it, here’s a guide to installing Oracle and PHP on Linux.

Oracle extension Designed for Oracle 7 or earlier. This is obsolete.

Here is an example of using the oci8 extension to query the emp table of the scott schema with bind parameters:

$conn = OCILogon("scott","tiger", $tnsName);

$stmt = OCIParse($conn,"select * from emp where empno > :emp order by empno");
$emp = 7900;
OCIBindByName($stmt, ':emp', $emp);
$ok = OCIExecute($stmt);
while (OCIFetchInto($stmt,$arr)) {
print_r($arr);
echo "
";
}

This generates the following output:

Array ( [0] => 7902 [1] => FORD [2] => ANALYST [3] => 7566 [4] => 03/DEC/81 [5] => 3000 [7] => 20 )


Array ( [0] => 7934 [1] => MILLER [2] => CLERK [3] => 7782 [4] => 23/JAN/82 [5] => 1300 [7] => 10 )

We also have many higher level PHP libraries that allow you to simplify the above code. The most popular are PEAR DB and ADOdb. Here are some of the differences between these libraries:

Feature PEAR DB 1.6 ADOdb 4.52

PEAR DB is good enough for simple web apps. But if you need more power, you can see ADOdb offers more sophisticated functionality. The rest of this article will concentrate on using ADOdb with Oracle. You can find out more about connecting to Oracle later in this guide.

ADOdb Example

In ADOdb, the above oci8 example querying the emp table could be written as:

include "/path/to/adodb.inc.php";
$db = NewADOConnection("oci8");
$db->Connect($tnsName, "scott", "tiger");

$rs = $db->Execute("select * from emp where empno>:emp order by empno",
array('emp' => 7900));
while ($arr = $rs->FetchRow()) {
print_r($arr);
echo "
";
}

The Execute( ) function returns a recordset object, and you can retrieve the rows returned using $recordset->FetchRow( ).

If we ignore the initial connection preamble, we can see the ADOdb version is much easier and simpler:

Oci8 ADOdb
$stmt = OCIParse($conn,
"select * from emp where empno > :emp");
$emp = 7900;
OCIBindByName($stmt, ':emp', $emp);
$ok = OCIExecute($stmt);

while (OCIFetchInto($stmt,$arr)) {
print_r($arr);
echo "
";
}
$recordset = $db->Execute("select * from emp where empno>:emp",
array('emp' => 7900));

while ($arr = $recordset->FetchRow()) {
print_r($arr);
echo "
";
}

2. ADOdb Query Semantics

You can also query the database using the standard Microsoft ADO MoveNext( ) metaphor. The data array for the current row is stored in the fields property of the recordset object, $rs. MoveNext( ) offers the highest performance among all the techniques for iterating through a recordset:

$rs = $db->Execute("select * from emp where empno>:emp", array('emp' => 7900));
while (!$rs->EOF) {
print_r($rs->fields);
$rs->MoveNext();
}

And if you are interested in having the data returned in a 2-dimensional array, you can use:

$arr = $db->GetArray("select * from emp where empno>:emp", array('emp' => 7900));

Now to obtain only the first row as an array:

$arr = $db->GetRow("select * from emp where empno=:emp", array('emp' => 7900));

Or to retrieve only the first field of the first row:

$arr = $db->GetOne("select ename from emp where empno=:emp", array('emp' => 7900));

For easy pagination support, we provide the SelectLimit function. The following will perform a select query, limiting it to 100 rows, starting from row 201 (row 1 being the 1st row):

$offset = 200; $limitrows = 100;
$rs = $db->SelectLimit('select * from table', $limitrows, $offset);

The $offset parameter is optional.

Array Fetch Mode

When data is being returned in an array, you can choose the type of array the data is returned in.

  1. Numeric indexes - use $connection->SetFetchMode(ADODB_FETCH_NUM).
  2. Associative indexes - the keys of the array are the names of the fields (in upper-case). Use $connection->SetFetchMode(ADODB_FETCH_ASSOC).
  3. Both numeric and associative indexes - use $connection->SetFetchMode(ADODB_FETCH_BOTH).

The default is ADODB_FETCH_BOTH for Oracle.

Caching

You can define a database cache directory using $ADODB_CACHE_DIR, and cache the results of frequently used queries that rarely change. This is particularly useful for SQL with complex where clauses and group-by’s and order-by’s. It is also good for relieving heavily-loaded database servers.

This example will cache the following select statement for 3600 seconds (1 hour):

$ADODB_CACHE_DIR = '/var/adodb/tmp';
$rs = $db->CacheExecute(3600, "select names from allcountries order by 1");

There are analogous CacheGetArray( ), CacheGetRow( ), CacheGetOne( ) and CacheSelectLimit( ) functions. The first parameter is the number of seconds to cache. You can also pass a bind array as a 3rd parameter (not shown above).There is an alternative syntax for the caching functions. The first parameter is omitted, and you set the cacheSecs property of the connection object:

$ADODB_CACHE_DIR = '/var/adodb/tmp';
$connection->cacheSecs = 3600;
$rs = $connection->CacheExecute($sql, array('id' => 1));

3. Using Prepare( ) For Frequently Used Statements

Prepare( ) is for compiling frequently used SQL statement for reuse. For example, suppose we have a large array which needs to be inserted into an Oracle database. The following will result in a massive speedup in query execution (at least 20-40%), as the SQL statement only needs to be compiled once:

$stmt = $db->Prepare('insert into table (field1, field2) values (:f1, :f2)');
foreach ($arrayToInsert as $key => $value) {
$db->Execute($stmt, array('f1' => $key, 'f2' => $val);
}

4. Working With LOBs

Oracle treats data which is more than 4000 bytes in length specially. These are called Large Objects, or LOBs for short. Binary LOBs are BLOBs, and character LOBs are CLOBs. In most Oracle libraries, you need to do a lot of work to process LOBs, probably because Oracle designed it to work in systems with little memory. ADOdb tries to make things easy by assuming the LOB can fit into main memory.

ADOdb will transparently handle LOBs in select statements. The LOBs are automatically converted to PHP variables without any special coding.

For updating records with LOBs, the functions UpdateBlob( ) and UpdateClob( ) are provided. Here’s a BLOB example. The parameters should be self-explanatory:

$ok = $db->Execute("insert into aTable (id, name, ablob)
values (aSequence.nextVal, 'Name', null)");
if (!$ok) return LogError($db->ErrorMsg());
# params: $tableName, $blobFieldName, $blobValue, $whereClause
$db->UpdateBlob('aTable', 'ablob', $blobValue, 'id=aSequence.currVal');

and the analogous CLOB example:

$ok = $db->Execute("insert into aTable (id, name, aclob)
values (aSequence.nextVal, 'Name', null)");
if (!$ok) return LogError($db->ErrorMsg());
$db->UpdateClob('aTable', 'aclob', $clobValue, 'id=aSequence.currVal');

Note that LogError( ) is a user-defined function, and not part of ADOdb.

Inserting LOBs is more complicated. Since ADOdb 4.55, we allow you to do this (assuming that the photo field is a BLOB, and we want to store $blob_data into this field, and the primary key is the id field):

$sql = "INSERT INTO photos ( ID, photo) ".
"VALUES ( :id, empty_blob() )".
" RETURNING photo INTO :xx"
;

$stmt = $db->PrepareSP($sql);
$db->InParameter($stmt, $id, 'id');
$blob = $db->InParameter($stmt, $blob_data, 'xx',-1, OCI_B_BLOB);
$db->StartTrans();
$ok = $db->Execute($stmt);
$db->CompleteTrans();

5. REF CURSORs

Oracle recordsets can be passed around as variables called REF Cursors. For example, in PL/SQL, we could define a function open_tab that returns a REF CURSOR in the first parameter:

TYPE TabType IS REF CURSOR RETURN TAB%ROWTYPE;

PROCEDURE open_tab (tabcursor IN OUT TabType,tablenames IN VARCHAR) IS
BEGIN
OPEN tabcursor FOR SELECT * FROM TAB WHERE tname LIKE tablenames;
END open_tab;

In ADOdb, we could access this REF Cursor using the ExecuteCursor() function. The following will find all table names that begin with ‘A’ in the current schema:

$rs = $db->ExecuteCursor("BEGIN open_tab(:refc,'A%'); END;",'refc');
while ($arr = $rs->FetchRow()) print_r($arr);

The first parameter is the PL/SQL statement, and the second parameter is the name of the REF Cursor.

6. In and Out Parameters

The following PL/SQL stored procedure requires an input variable, and returns a result into an output variable:

PROCEDURE data_out(input IN VARCHAR, output OUT VARCHAR) IS
BEGIN
output := 'I love '||input;
END;

The following ADOdb code allows you to call the stored procedure:

$stmt = $db->PrepareSP("BEGIN adodb.data_out(:a1, :a2); END;");
$input = 'Sophia Loren';
$db->InParameter($stmt,$input,'a1');
$db->OutParameter($stmt,$output,'a2');
$ok = $db->Execute($stmt);
if ($ok) echo ($output == 'I love Sophia Loren') ? 'OK' : 'Failed';

PrepareSP( ) is a special function that knows about bind parameters. The main limitation currently is that IN OUT parameters do not work.

Bind Parameters and REF CURSORs

We could also rewrite the REF CURSOR example to use InParameter (requires ADOdb 4.53 or later):

$stmt = $db->PrepareSP("BEGIN adodb.open_tab(:refc,:tabname); END;");
$input = 'A%';
$db->InParameter($stmt,$input,'tabname');
$rs = $db->ExecuteCursor($stmt,'refc');
while ($arr = $rs->FetchRow()) print_r($arr);

Bind Parameters and LOBs

You can also operate on LOBs. In this example, we have IN and OUT parameters using CLOBs.

$text = 'test test test';
$sql = "declare rs clob; begin :rs := lobinout(:sa0); end;";
$stmt = $conn -> PrepareSP($sql);
$conn -> InParameter($stmt,$text,'sa0', -1, OCI_B_CLOB); # -1 means variable length
$rs = '';
$conn -> OutParameter($stmt,$rs,'rs', -1, OCI_B_CLOB);
$conn -> Execute($stmt);
echo "return = ".$rs."
";

Similarly, you can use the constant OCI_B_BLOB to indicate that you are using BLOBs.

Reusing Bind Parameters with CURSOR_SHARING=FORCE

Many web programmers do not care to use bind parameters, and prefer to enter the SQL directly. So instead of:

$arr = $db->GetArray("select * from emp where empno>:emp", array('emp' => 7900));

They prefer entering the values inside the SQL:

$arr = $db->GetArray("select * from emp where empno>7900");

This reduces Oracle performance because Oracle will reuse compiled SQL which is identical to previously compiled SQL. The above example with the values inside the SQL is unlikely to be reused. As an optimization, from Oracle 8.1 onwards, you can set the following session parameter after you login:

ALTER SESSION SET CURSOR_SHARING=FORCE

This will force Oracle to convert all such variables (eg. the 7900 value) into constant bind parameters, improving SQL reuse.

More speedup tips.

7. Dates and Datetime in ADOdb

There are two things you need to know about dates in ADOdb.

First, to ensure cross-database compability, ADOdb assumes that dates are returned in ISO format (YYYY-MM-DD H24:MI:SS).

Secondly, since Oracle treats dates and datetime as the same data type, we decided not to display the time in the default date format. So on login, ADOdb will set the NLS_DATE_FORMAT to ‘YYYY-MM-DD’. If you prefer to show the date and time by default, do this:

$db = NewADOConnection('oci8');
$db->NLS_DATE_FORMAT = 'RRRR-MM-DD HH24:MI:SS';
$db->Connect($tns, $user, $pwd);

Or execute:

$sql = "ALTER SESSION SET NLS_DATE_FORMAT = 'RRRR-MM-DD HH24:MI:SS'";
$db->Execute($sql);

If you are not concerned about date portability and do not use ADOdb’s portability layer, you can use your preferred date format instead.

8. Database Portability Layer

ADOdb provides the following functions for portably generating SQL functions as strings to be merged into your SQL statements:

Function Description
DBDate($date) Pass in a UNIX timestamp or ISO date and it will convert it to a date string formatted for INSERT/UPDATE
DBTimeStamp($date) Pass in a UNIX timestamp or ISO date and it will convert it to a timestamp string formatted for INSERT/UPDATE
SQLDate($date, $fmt) Portably generate a date formatted using $fmt mask, for use in SELECT statements.
OffsetDate($date, $ndays) Portably generate a $date offset by $ndays.
Concat($s1, $s2, …) Portably concatenate strings. Alternatively, for mssql use mssqlpo driver, which allows || operator.
IfNull($fld, $replaceNull) Returns a string that is the equivalent of MySQL IFNULL or Oracle NVL.
Param($name) Generates bind placeholders, using ? or named conventions as appropriate.
$db->sysDate Property that holds the SQL function that returns today’s date
$db->sysTimeStamp Property that holds the SQL function that returns the current timestamp (date+time).
$db->concat_operator Property that holds the concatenation operator
$db->length Property that holds the name of the SQL strlen function.
$db->upperCase Property that holds the name of the SQL strtoupper function.
$db->random Property that holds the SQL to generate a random number between 0.00 and 1.00.
$db->substr Property that holds the name of the SQL substring function.

ADOdb also provides multiple oracle oci8 drivers for different scenarios:

Driver Name Description
oci805 Specifically for Oracle 8.0.5. This driver has a slower SelectLimit( ).
oci8 The default high performance driver. The keys of associative arrays returned in a recordset are upper-case.
oci8po The portable Oracle driver. Slightly slower than oci8. This driver uses ? instead of :bindvar for binding variables, which is the standard for other databases. Also the keys of associative arrays are in lower-case like other databases.

Here’s an example of calling the oci8po driver. Note that the bind variables use question-mark:

$db = NewADOConnection('oci8po');
$db->Connect($tns, $user, $pwd);
$db->Execute("insert into atable (f1, f2) values (?,?)", array(12, 'abc'));

9. Connecting to Oracle

Before you can use ADOdb, you need to have the Oracle client installed and setup the oci8 extension. This extension comes pre-compiled for Windows (but you still need to enable it in the php.ini file). For information on compiling the oci8 extension for PHP and Apache on Unix, there is an excellent guide at oracle.com.

Should You Use Persistent Connections

One question that is frequently asked is should you use persistent connections to Oracle. Persistent connections allow PHP to recycle existing connections, reusing them after the previous web pages have completed. Non-persistent connections close automatically after the web page has completed. Persistent connections are faster because the cost of reconnecting is expensive, but there is additional resource overhead. As an alternative, Oracle allows you to pool and reuse server processes; this is called Shared Server (also known as MTS).

The author’s benchmarks suggest that using non-persistent connections and the Shared Server configuration offer the best performance. If Shared Server is not an option, only then consider using persistent connections.

Connection Examples

Just in case you are having problems connecting to Oracle, here are some examples:

a. PHP and Oracle reside on the same machine, use default SID, with non-persistent connections:

$conn = NewADOConnection('oci8');
$conn->Connect(false, 'scott', 'tiger');

b. TNS Name defined in tnsnames.ora (or ONAMES or HOSTNAMES), eg. ‘myTNS’, using persistent connections:

$conn = NewADOConnection('oci8');
$conn->PConnect(false, 'scott', 'tiger', 'myTNS');

or

$conn->PConnect('myTNS', 'scott', 'tiger');

c. Host Address and SID

$conn->connectSID = true;
$conn->Connect('192.168.0.1', 'scott', 'tiger', 'SID');

d. Host Address and Service Name

$conn->Connect('192.168.0.1', 'scott', 'tiger', 'servicename');

e. Oracle connection string:

$cstr = "(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=$host)(PORT=$port))
(CONNECT_DATA=(SID=$sid)))";
$conn->Connect($cstr, 'scott', 'tiger');

f. ADOdb data source names (dsn):

$dsn = 'oci8://user:pwd@tnsname/?persist';  # persist is optional
$conn = ADONewConnection($dsn); # no need for Connect/PConnect

$dsn = 'oci8://user:pwd@host/sid';
$conn = ADONewConnection($dsn);

$dsn = 'oci8://user:pwd@/'; # oracle on local machine
$conn = ADONewConnection($dsn);

With ADOdb data source names, you don’t have to call Connect( ) or PConnect( ).

10. Error Checking

The examples in this article are easy to read but a bit simplistic because we ignore error-handling. Execute( ) and Connect( ) will return false on error. So a more realistic way to call Connect( ) and Execute( ) is:

function InvokeErrorHandler()
{
global $db; ## assume global
MyLogFunction($db->ErrorNo(), $db->ErrorMsg());
}
if (!$db->Connect($tns, $usr, $pwd)) InvokeErrorHandler();

$rs = $db->Execute("select * from emp where empno>:emp order by empno",
array('emp' => 7900));
if (!$rs) return InvokeErrorHandler();
while ($arr = $rs->FetchRow()) {
print_r($arr);
echo "
";
}

You can retrieve the error message and error number of the last SQL statement executed from ErrorMsg( ) and ErrorNo( ). You can also define a custom error handler function. ADOdb also supports throwing exceptions in PHP5.

Handling Large Recordsets (added 27 May 2005)

The oci8 driver does not support counting the number of records returned in a SELECT statement, so the function RecordCount() is emulated when the global variable $ADODB_COUNTRECS is set to true, which is the default. We emulate this by buffering all the records. This can take up large amounts of memory for big recordsets. Set $ADODB_COUNTRECS to false for the best performance.This variable is checked every time a query is executed, so you can selectively choose which recordsets to count.

11. Other ADOdb Features

Schema generation. This allows you to define a schema using XML and import it into different RDBMS systems portably.

Performance monitoring and tracing. Highlights of performance monitoring include identification of poor and suspicious SQL, with explain plan support, and identifying which web pages the SQL ran on.

12. Download

You can download ADOdb from sourceforge. ADOdb uses a BSD style license. That means that it is free for commercial use, and redistribution without source code is allowed.

13. Resources

Thursday, September 18, 2008

FreeBSD Apache HTTP Accept Filter Error

When Apache web server is starting up in FreeBSD system, Apache loads succcessfully and web server functioning properly, but the following warning error occurs:

[warn] (2)No such file or directory:
Failed to enable the ‘httpready’ Accept Filter

The resolution to the above problem is to a accf_http module, which function is to buffer incoming connections until a certain complete HTTP requests arrive, into FreeBSD kernel by using kernel linker:

kldload accf_http

To permanently load enable HTTP Accept Filter FreeBSD kernel module (accf_http), add the following line into /boot/loader.conf:

accf_http_load=”YES”

Note: The default settings is located in /boot/defaults/loader.cnf. To see the related settings about accf, use:

grep accf /boot/defaults/loader.conf

which will returns:

accf_data_load=”NO” # Wait for data accept filter
accf_http_load=”NO” # Wait for full HTTP request accept filter

Bash on freebsd

By default, many Linux distro’s have a nice BASH prompt. If you’ve just installed FreeBSD, you might be wondering how to get a shell prompt you’re more used to, ie. BASH. By default, your shell is CSH. There are a few ways to install software using FreeBSD, whichever way you choose you will have to install BASH. I personally prefer using ports, but for simplicity sake, pkg_add -r bash will work. Once installed, you might notice the prompt isn’t the BASH prompt you know and love. Fear not, there is a way to get things set up the way you like it.

Here’s how you do it!

First, we need to chsh to bring up our user preferences, change from CSH (C SHell) to BASH (Bourne Again SHell). Enter the full path to BASH, usually /usr/local/bin/bash).

Bash is optionally installed by the user and is not part of the base system, therefore there is no default ~/.bashrc. You have to make your own. Simply create a blank file, name it .bashrc and save it in your (the user’s) home drive.

Then add the following line to .bashrc.

PS1="[\u@\h \w]# “

That should give you:

[username@hostname /full/path/]# your commands here

This command prompt format is quite popular, for example it’s the default for at least the last few releases of Red Hat and Fedora.

Setting bash as default shell

Q. I am using FreeBSD and I would like to setup bash as default shell?

A. CSH is default shell under FreeBSD. First, make sure bash is installed. Type bash to see if you can execute bash:
$ bash
If you get command not found error. Use pkg_add command to install bash
# pkg_add -r -v bash

Now to setup bash as shell type chsh command.

chsh -s /path/to/shell for curent user

or

chsh -s /path/to/shell {user-name}

chsh changes the user login shell. This determines the name of the user's initial login command. A normal user may only change the login shell for her own account, the super user may change the login shell for any account. To setup bash default shell for user vivek, type command:

$ which bash
Output:

/usr/local/bin/bash

Now change shell:
$ chsh -s /usr/local/bin/bash vivek
Output:

Password

Provide your login password. Verify that shell is changed:
$ grep ^vivek /etc/passwd

You can also edit /etc/passwd file and change shell (you need to login as root user):
# vi /etc/passwd
Last filed is shell
From
vivek:x:1000:1000:vivek,,,:/home/vivek:/bin/sh
To
vivek:x:1000:1000:vivek,,,:/home/vivek:/usr/local/bin/bash

Save and close the file.

install FAMP (FreeBSD, Apache, Mysql, PHP)

There are three ways to install software on a FreeBSD system. You can do everything from ‘first principle’: download source code, compile and install. The second way is to use pkg_add to install pre-built binary packages. pkg_add is able to resolve dependencies and it will automatically download and install these as well.

The third way is to use Ports. The Ports Collection is a set of makefiles, patches and description files placed in /usr/ports. To install a port, you just need to browse to the relevant subdirectory and type ‘make install’ For example:

cd /usr/ports/editors/vim
make install

This will tell the Ports System to download the vim source code, compile and install it on your system. You are given the chance to choose from a set of compile time options if there are any. The Ports System is able to resolve dependencies too.

We will be installing the required software from ports because it gives us both flexibility (we are able to select compile time options) and convenience (it resolves dependencies and fetches the source code for us). Software installed from Ports can also be upgraded easily.

Let’s use Portsnap to make sure that we have the latest snapshot of the Ports Collection. Portsnap is a system for securely distributing the Ports tree. On a brand new installation of FreeBSD 6.1, as root:

portsnap fetch
portsnap extract

You now are the proud owner of an up to date Ports Collection. From now on, if you want to update your Ports tree, just do:

portsnap fetch update

More information on Portsnap can be found in the relevant section of the excellent FreeBSD Handbook.

You can use cvsup as an alternative to Portsnap. However, Portsnap snapshots are cryptographically signed and the system uses less bandwidth. You can use pkg_version to check whether you have any outdated ports on your system:

pkg_version -v

To update all the outdated ports with one command:

portupgrade –a

portupgrade –ai will ask for confirmation before updating each port. If you don’t have portupgrade installed, you can install it by doing:

cd /usr/ports/sysutils/portupgrade
make install

It is also a good idea to do

make clean

This gets rid of the working subdirectory that contains the temporary files during compilation. This can be shortened to ‘cd /usr/ports/sysutils/portupgrade; make install clean’. More information on how you can keep your system up to date can be found here. Now that we have an updated Ports Collection, let’s get down to business.

Install Apache:

cd /usr/ports/www/apache22
make install clean

Install MySQL:

cd /usr/ports/database/mysql50-server
make install clean

Configure the php5 port:

cd /usr/ports/lang/php5
make config

Deselect option CLI. Selecting to build the CLI version rendered php apache module unsuable for me for some reason. A quick googling around told me that I was not alone in having this problem.

Install PHP:

make install clean

Install PHP extensions:

cd /usr/ports/lang/php5-extensions
make install clean

Just choose what extensions you want to install. Install the php5-mysql extension:

cd /usr/ports/databases/php5-mysql
make install clean

Let’s start our new daemons:

/usr/local/etc/rc.d/apache22.sh start
/usr/local/etc/rc.d/mysql-server start

You should now be able to browse to your web address and see the cheerful exclamation: ‘It works!’ To make sure that Apache and MySQL start whenever the server is rebooted, add these lines to /etc/rc.conf:

apache22_enable=”YES”
mysql_enable=”YES”

Software installed through ports will have their configuration files in /usr/local/etc. The base system configuration files live in /etc. This makes for a clean, consistent filesystem layout. Let’s configure our newly installed software.

To make Apache PHP-aware, add the following lines to /usr/local/etc/apache22/httpd.conf:

AddType application/x-httpd-php .php
AddType application/x-httpd-php-source .phps

Go through the configuration file and customise to your needs. The PHP port has created a php.ini-recommended file for us in /usr/local/etc/. Let’s use that as a starting point:

cp /usr/local/etc/php.ini-recommended /usr/local/etc/php.ini

Now edit /usr/local/etc/php.ini to suit your needs. Restart Apache to apply the configuration changes:

apachectl graceful

By default, no password is set for the MySQL root user. You should set a password for that user at this point:

mysqladmin -u root password "newpassword"

Replace “newpassword” with your own. MySQL comes with two anonymous accounts set up. These accounts don’t have any passwords assigned by default. It is a good idea to just delete them. Use the MySQL client that came with the database server:

mysql –u root -p

When asked, type in your newly created database root password. At the MySQL prompt, issue the following command:

delete from mysql.user where user = '';
flush privileges;

To exit from the MySQL client, just type ‘exit’.

That’s it! I now suggest that you take a look at the output of phpinfo() to make sure that everything is in order.

Wednesday, September 17, 2008

Tutorial on installing php using freebsd port

Choosing which port to use

In the past there were several ports for PHP such as /www/mod-php5, /lang/php5-cli, and /lang/php5. Since the release of PHP 5.1.14 there is now only /lang/php5 This port now allows you to choose if you want to install the CLI, CGI, and Apache module.

CLI stands for command line interpreter. It is used for running PHP scripts from the command line and makes creating shell scripts very simple if you already know PHP. The Apache PHP Module is disabled by default, so make SURE that if you plan to use this for web work that you enable it.

Installing the port

Since all PHP ports are now combined you will need to configure it to be sure the parts you need are built.

# cd /usr/ports/lang/php5
# make config
# make install

When you run make config you will be shown a list of options. To use PHP with Apache make sure the Apache Module box is selected.

Once php has installed you will need to install the extra modules for things such as MySQL. These modules are all located in the ports. Some of the most common modules are

/usr/ports/databases/php5-mysql - MySQL Database
/usr/ports/www/php5-session - Sessions
/usr/ports/graphics/php5-gd - Graphics Library

Adding the PHP 5 module to Apache

Apache needs the following lines in the httpd.conf file to use php. These lines should already be added by the port but if you have problems you should double check your httpd.conf file. Note that Apache 2.x does not need the AddModule line.

# Apache 1.3.x
LoadModule php5_module libexec/apache/libphp5.so
AddModule mod_php5.c
# Apache 2.x
LoadModule php5_module libexec/apache/libphp5.so

If you installed using the port and had apache installed already it should do this automatically for you.

Next find your DirectoryIndex section in your httpd.conf file. Apache is set up for PHP 4, but not PHP 5 currently so you will need to modify it and change the 4s to 5s like this.

           DirectoryIndex index.php index.php3 index.html   
DirectoryIndex index.php3 index.html

This code is telling Apache to open index.php first you have the PHP 5 module loaded. You can change the order as you wish. Or if you just wanted to skip it you could simply add the following line to the httpd.conf file since you know you are going to have php 5.

DirectoryIndex index.php index.html index.htm

Now apache just needs to know what it should parse the PHP files with. These two lines should be added to the httpd.conf file, and can be put at the bottom if needed.

AddType application/x-httpd-php .php
AddType application/x-httpd-php-source .phps

If want to use PHP code inside of .htm files you can just add on those extensions.

AddType application/x-httpd-php .php .htm .html

Configuring PHP

Settings for PHP are stored in /usr/local/etc/php.ini You will need to create this file by copying it from /usr/local/etc/php.ini-dist

# cp /usr/local/etc/php.ini-dist /usr/local/etc/php.ini

In this file you can set the memory limit for programs. Turn on global variables if you must, set the max file upload size, and everything else you need.

Testing PHP

Once you have restarted Apache so the changes take effect you are ready to test it. To test it run the following command to create a php file that you can attempt to run

# echo "" >> /usr/local/www/data/test.php

Then point your web browser to http://yourdomain.com/test.php and if it works you will see several pages of information on your PHP settings. If it did not work you will see only the text you typed in.

Tutorial on installing php using freebsd port

Choosing which port to use

In the past there were several ports for PHP such as /www/mod-php5, /lang/php5-cli, and /lang/php5. Since the release of PHP 5.1.14 there is now only /lang/php5 This port now allows you to choose if you want to install the CLI, CGI, and Apache module.

CLI stands for command line interpreter. It is used for running PHP scripts from the command line and makes creating shell scripts very simple if you already know PHP. The Apache PHP Module is disabled by default, so make SURE that if you plan to use this for web work that you enable it.

Installing the port

Since all PHP ports are now combined you will need to configure it to be sure the parts you need are built.

# cd /usr/ports/lang/php5
# make config
# make install

When you run make config you will be shown a list of options. To use PHP with Apache make sure the Apache Module box is selected.

Once php has installed you will need to install the extra modules for things such as MySQL. These modules are all located in the ports. Some of the most common modules are

/usr/ports/databases/php5-mysql - MySQL Database
/usr/ports/www/php5-session - Sessions
/usr/ports/graphics/php5-gd - Graphics Library

Adding the PHP 5 module to Apache

Apache needs the following lines in the httpd.conf file to use php. These lines should already be added by the port but if you have problems you should double check your httpd.conf file. Note that Apache 2.x does not need the AddModule line.

# Apache 1.3.x
LoadModule php5_module libexec/apache/libphp5.so
AddModule mod_php5.c
# Apache 2.x
LoadModule php5_module libexec/apache/libphp5.so

If you installed using the port and had apache installed already it should do this automatically for you.

Next find your DirectoryIndex section in your httpd.conf file. Apache is set up for PHP 4, but not PHP 5 currently so you will need to modify it and change the 4s to 5s like this.




DirectoryIndex index.php index.php3 index.html


DirectoryIndex index.php3 index.html




DirectoryIndex index.php index.html index.htm


DirectoryIndex index.html


This code is telling Apache to open index.php first you have the PHP 5 module loaded. You can change the order as you wish. Or if you just wanted to skip it you could simply add the following line to the httpd.conf file since you know you are going to have php 5.

DirectoryIndex index.php index.html index.htm

Now apache just needs to know what it should parse the PHP files with. These two lines should be added to the httpd.conf file, and can be put at the bottom if needed.

AddType application/x-httpd-php .php
AddType application/x-httpd-php-source .phps

If want to use PHP code inside of .htm files you can just add on those extensions.

AddType application/x-httpd-php .php .htm .html

Configuring PHP

Settings for PHP are stored in /usr/local/etc/php.ini You will need to create this file by copying it from /usr/local/etc/php.ini-dist

# cp /usr/local/etc/php.ini-dist /usr/local/etc/php.ini

In this file you can set the memory limit for programs. Turn on global variables if you must, set the max file upload size, and everything else you need.

Testing PHP

Once you have restarted Apache so the changes take effect you are ready to test it. To test it run the following command to create a php file that you can attempt to run

# echo "" >> /usr/local/www/data/test.php

Then point your web browser to http://yourdomain.com/test.php and if it works you will see several pages of information on your PHP settings. If it did not work you will see only the text you typed in.

install and configure mysql on freebsd

cd /usr/ports/databases/mysql41-server/
make install clean
/usr/local/bin/mysql_install_db
chown -R mysql /var/db/mysql/
chgrp -R mysql /var/db/mysql/
/usr/local/bin/mysqld_safe –user=mysql &
/usr/local/bin/mysqladmin -u root password newpassword

echo 'mysql_enable="YES"'>> /etc/rc.conf


That's all the steps for installing and configuring mysql in freebsd..so easy is it?

Sunday, September 14, 2008

Configure SVN using windows

I came across this google search, but the page seemed to be deleted or moved away, so here is the original article on how to configure SVN using windows….the credit goes to the original author and not to me…I have just blogged it for reference purposes…

Setting up a Subversion Server under Windows

I talked a little bit about what Subversion is in my previous post. Now, let’s get it set it up in Windows.

Luckily for us, Joe White has done most of the work already; he has a tremendously helpful post that documents how to set up Subversion. I’ll see if I can streamline his excellent post a bit further, and illustrate it with screenshots.

A) Download Subversion

You’ll need the latest version of..

B) Install Subversion

  1. Unzip the Windows binaries to a folder of your choice. I chose c:\program files\subversion\ as my path.
  2. Now, add the subversion binaries to the path environment variable for the machine. I used %programfiles%\subversion\bin\
  3. You’ll also need another environment variable, SVN_EDITOR, set to the text editor of your choice. I used c:\windows\notepad.exe

C) Create a Repository

  1. Open a command prompt and type
    svnadmin create "c:\Documents and Settings\Subversion Repository"
  2. Navigate to the folder we just created. Within that folder, uncomment the following lines in the /conf/svnserve.conf file:
    [general]
    anon-access = read
    auth-access = write
    password-db = passwd

    Next, uncomment these lines in the /conf/passwd file:

    [users]
    harry = harryssecret
    sally = sallyssecret

D) Verify that everything is working

  1. Start the subversion server by issuing this command in the command window:
    svnserve --daemon --root "C:\Documents and Settings\Subversion Repository"
  2. Create a project by opening a second command window and entering this command:
    svn mkdir svn://localhost/myproject

    It’s a standard Subversion convention to have three folders at the root of a project:

    /trunk
    /branches
    /tags

  3. At this point, Notepad should launch:

    Enter any comment you want at the top of the file, then save and exit.

  4. You’ll now be prompted for credentials. In my case I was prompted for the administrator credentials as well:
    Authentication realm:  0f1a8b11-d50b-344d-9dc7-0d9ba12e22df
    Password for 'Administrator': *********
    Authentication realm: 0f1a8b11-d50b-344d-9dc7-0d9ba12e22df
    Username: sally
    Password for 'sally': ************

    Committed revision 1.

    Congratulations! You just checked a change into Subversion!

E) Start the server as a service

  1. Stop the existing command window that’s running svnserve by pressing CTRL+C.
  2. Copy the file SVNService.exe from the zip file of the same name to the subversion\bin folder.
  3. Install the service by issuing the following commands:
    svnservice -install --daemon --root "C:\Documents and Settings\Subversion Repository"
    sc config svnservice start= auto
    net start svnservice
  4. Test the new service by listing all the files in the repository:
    svn ls svn://localhost/

    You should see the single project we created earlier, myproject/

F) Set up the shell extension

  1. Run the TortoiseSVN installer. It will tell you to restart, but you don’t need to.
  2. Create a project folder somewhere on your hard drive. Right click in that folder and select “SVN Checkout…”

    type svn://localhost/myproject/ for the repository URL and click OK.

  3. Create a new file in that directory. Right click the file and select “TortoiseSVN, Add”

  4. The file hasn’t actually been checked in yet. Subversion batches any changes and commits them as one atomic operation. To send all your changes to the server, right click and select “SVN Commit”:

And we’re done! You now have a networked Subversion server and client set up on your machine. Note that the default port for svnserve is 3690.

Saturday, September 13, 2008

Pelik tapi benar..rumput makan lembu...

Situasi Pertama

Ali : Hoho Mok Minoh..Guane nengung tuu..tadok kije ke??
Mok Minoh : Bakpe dok nengungnya..ikan2 aku abih makan kkucing...base nguh kucing...

Situasi Kedua dimana Pok Ali merungut di satu petang...Dia berkata..."Celake betol lembu...
abis rumput-rumput aku makan lembu..kaca nguh..lembu aku nok makan mende dok tau petang ni..

Kalau dipikir2 balik mana logik ikan makan kucing atau rumput makan lembu...sepatutnya kucing yang makan ikan atau pon lembu yang makan rumput. tapi di terengganu ini adalah perkara biasa dimana sudah menjadi kebiasaan org trg ni untuk bercakap dalam bahasa yang mungkin org-org luar trg atau kelantan tak paham. Yakni ayat-ayat yang reverse meaning seperti situasi-situasi yang telah disebutkan. Sebenarnya maksudnye sama cuma cara ayat-ayat disebut agak berbeza dr segi itonasi nyer..Mungkin kalau diucapkan tanpa itonasi yang betol akan memberi makna yang salah..

So sekiranye anda berasa di Trg atau Kelantan, jangan la terkejut kalau anda mendengar perkataan-perkataaan begini diucapkan. Anda perlu reverse balik ape yang diperkatakan itu. hehe

renung-renungkan dan selamat beramal.

Thursday, September 11, 2008

Google Chrome

Google Chrome Web Browser - Fast, smooth browsing




















Assalamualaikum Warahmatullahi Wabarakatuh.

Dear brothers and sisters,

Have you tried Google Chrome Web Browser yet?

I have been using it recently and find the browser to be very reliable. Web pages open in a jiffy and playing back You Tube videos has never been easier.

The browser works very smoothly and is quite stable. It has yet to crash ever since I started using it and is more reliable than other browsers on the net.

Try it today and see for yourselves.

Download the browser at http://www.google.com/chrome

I am quite satisfied with it. I have no problems recommending it to you.

Happy surfing.

12nd September 2008

I woke up 5.05 am today..then i went to toilet..get some wuduk..coz not semayang isyak yet..hehehe..i slept after pray solat magrib with towel still in my body..coz i planned to take a shower and go to mosque for taraweh but i slept after i put my head onto my beautiful pillow.hehe..

then my lovely mum call me for sahur..coz it's i am gonna late oredi for sahur...
i just took a senduk nasi with daging goreng n milo kosong. After that, i niat pose first..
my niat is like this.."sahaja aku pose esok hari bagi menunaikan pose bulan ramandhan kerana Allah Taala"

hhehehe
then switch on my pc...n blogging until 12 pm..hehehe...now 12 pm oredi..i have to stop and get to toilet....not mandi yet...n 1.00 pm neeed to go to mosque for solat jumaat.

Wednesday, September 10, 2008

Subversion: The "sequel" to CVS

Subversion is a sort of open-source "sequel" to CVS, the classic open-source version control system that dates back to 1989. The Subversion FAQ is very clear about this:

Why does this project exist? To take over the CVS user base. Specifically, we're writing a new version control system that is very similar to CVS, but fixes many things that are broken. See our front page.

And it seems to be very successful as a CVS replacement. Simon Tatham, the developer of PuTTY, switched from CVS to Subversion in late 2004 and has almost uniformly positive things to say about it:

I've described my experiences so far of migrating from CVS to Subversion, and using Subversion. In particular:

  • userv can be used to give Subversion a security model vastly superior to that of CVS.
  • FSFS is king, and permits anonymous access with a high confidence of security. This means Subversion 1.1.
  • Migrating from CVS is easy if you don't mind the way cvs2svn sets things up for you, but gets a lot more difficult the more perfectionist you get. However, if you only do it once it's worth putting some effort in!

Although I've described some issues above which might or might not be considered bugs in Subversion, in general they're easy enough to work around, and in most respects I think Subversion amply fulfills its promise of being ‘CVS, only done properly’. I like it, and I don't regret migrating. I'd do it again.

Subversion also won the 2005 Jolt Award for best Change and Configuration Management Tool:

Subversion provides an attractive alternative to expensive, high-end, source code management packages. Several features make it particularly appealing: Check-ins of multiple files are done as a single changeset, and check-ins of changesets are atomic—all modified files are checked in as a single transaction, or none of them are. Subversion rolls back a partially completed check-in should a problem occur. This step prevents incorrect versioning in case of a conflict or error. Subversion supports check-ins of entire directories as a single commit, which makes for facile updates of entire projects. It also supports the use of metadata—either text or binary—that can be associated with any and all files. Add easy, inexpensive branching and multiple networking protocols, and you have a winning product. On top of that, Subversion offers a server that's more portable than CVS and more functional than Windows, and that can run in stand-alone mode or as a module in Apache 2.x. All these goodies add up to the best version-control product on the market today. The fact that it's free and open-source is just a bonus.

The name Subversion is also quite clever. It reminds me of the Church of the Subgenius:

But getting Subversion set up in Windows is a little bit daunting. I'll cover how to do that in my next post.

waiting and waiting......

7.10 am...actually i am waiting for my sister get ready to school. Coz my occasional routines everyday to send her to her school along the way i get to my office in UMT.
She alwaz late prepare herself everyday...ish2...

my sister finished oredi seeting up...Happy morning!!!!

Tuesday, September 09, 2008

Exam Undang-Undang Kerajaan

Do u believe it?? i took a general exam..hahaha..
I just finished my paper in TER...short form for Terengganu Equestrian Resort.

Last time in private, i've focus for profesional cert such as RHCE, LPI, CCNA, CCNP, MCSE and etc. i just passed JNCIS before i joined UMT and CCNA and LPI gonna be. i can get allowance for each cert. Now on, not anymore.. i just need to think about the boring exam like PTK and sort of things like dat. No need to perah otak to study technical things like router, OS, programming things and database.
Now onward, what i need to do is buy General Order's book, Pekeliling Perkhidmantan and etc. And study those books. hehehe..wahhhhh...those silly bus is like my Kenegeraaan's subject in University.huhu..

before this, i finished two paper already which is Induksi and BTN paper.. so easy..coz BTN paper is all objective and for Induksi i got soalan bocor for subjective paper from my friend, Jili Nate Gilor. haha...

Why i need to do all of this..??
Coz i need to pass BTN, Induksi and other two exams to confirm in UMT. After that i just need to take PTK for promotion to other level and for salary increment.huhuhu... this is rules in government sector. Unlike private sector, to confirm we need to perform in our work. here i have 3 years probation. so i have to confirn within this duration. keep it try...gud luck for me...do my best..

Sunday, September 07, 2008

Cara untuk Melajukan Firefox

"Pengenalan Ringkas -

Mozilla Firefox - juga dipanggil 'Firefox' - secara umumnya cuma satu Internet browser yang percuma, sama seperti Internet Explorer sebagai contoh. Mozilla dengan erti kata lain menyediakan satu kumpulan software lengkap untuk aplikasi web seperti browser, bersama mail client, chat client dan banyak lagi.

Firefox Terlalu Popular ? Jadi, mengapa Firefox terlalu popular berbanding produk-produk Mozilla yang lain?


Kerana ia merupakan satu aplikasi ringkas, jadi anda tidak perlu bertukar kepada aplikasi lain secara tiba-tiba sedangkan anda amat menyukai aplikasi yang anda gunakan sekarang seperti Outlook Express, mIRC, dll. Anda boleh muat turun Firefox dan gunaknnya bersam-sama aplikasi web-based yang lain - Internet Explorer pun boleh - tanpa perlu menghadi sebarang masalah.

Anda tidak perlu lagi install pelbagai software untuk setiap fungsi yang anda ingin gunakan, cuma tambahkan extension yang anda ingin gunakan dalam Firefox. Sekurang-kurangnya anda dapat jimatkan ruang cakera keras anda untuk install pelbagai aplikasi yang anda ingin gunakan.

"Ok" anda jawab, "Saya malas...kenapa saya perlu muat turun. install dan tweak Firefox - kenapa saya tidak boleh terus je gunakan Internet Explorer?"

Pertama sekali, tiada sebab anda tidak boleh gunakan kedua-dua browser sekaligus. Artikel ini bukan untuk membuktikan bahawa Microsoft merupakan Jin Ifrit. Internet Explorer 7 merupakan browser yang bagus, Tetapi alasan yang paling kukuh untuk anda bertukar kepada Firefox adalah kerana top-notch security, Firefox ada ciri-ciri tamabahan yang tidak mampu difikirkan oleh logik akal yang sudah pasti tiada dalam Internet Explorer. Firefox boleh di'customize' dalam pelbagai cara yang mana tidak berlaku pada Internet Explorer.

Firefox berubah/evolusi dengan pantas kerana ia meruapan aplikasisumber terbuka / open source software, ia ada komuniti dan developer yang sanggup menghabiskan banyak masa dan usaha dalam memberikan respon yang pantas kepada keperluan pengguna, membaiki sebarang 'vulnerability' yang ingin 'exploit' secepat mungkin, dan merubah Firefox kepada satu browser yang perlu ditandingi. Secra fakta, developer memerlukan idea daripada pengguna untuk memberikan idea kepada mereka supaya ciri-ciri baru dapat dicipta dalam versi Firefox yang akan datang.

Tip Untuk Melajukan Surfing Menggunakan Firefox.
- Credit to Tok_Aslim.

kepada pengguna Streamyx... kelajuan boleh mencecah 5 kali bila surfing dan telah terbukti...

1. Buka firefox anda, dantaip kat address tu ----> about:config
2. Right click kat mana2 pastu select New -> Integer ...... dan paste skrip berikut -----------> nglayout.initialpaint.delay .............. pastu set 300

kalau tidak berpuas hati dgn 300.. boleh disetkan kepada -----> 0

kalau masih tidak suka dgn kelajuannya... cuba yg ini pula, type skali lagi ----> about:config

1. tengok config tu dan scroll down sampai jumpa skrip tersebut
network.http.pipelining
network.http.proxy.pipelining
network.http.pipelining.maxrequests

2. double click kat situ utk tukar false tu kepada true, contohnya -

network.http.pipelining - true
network.http.proxy.pipelining - true
network.http.pipelining.maxrequests - 30 (sekali request 30 mai)

3. pastu type skali lg -----> about:config dan select New -> Integer dan paste skrip tersebut --->
nglayout.initialpaint.delay
set kepada 0

dan kita restart firefox (tutup dan buka semula)

kita boleh test... sepatutnya akan melajukan 2 hingga 3 kali lebih laju daripada biasa.. Selamat Mencuba......

Kredit to putera aslim at webkl.net

Tuesday, September 02, 2008

Doa Selepas Solat


Ertinya:

Dengan nama Allah yang Maha Pemurah lagi Maha Penyayang segala puji bagi Allah Tuhan Pentadbir seluruh alam. Pujian yang melengkapi nikmat-Nya, dan pujian yang menambahkan kelebihan-Nya, wahai Tuhan kami, bagiMu segala puja-puji sebagaimana patut kerana kebesaran zatMu dan agong kekuasaanMu


Ertinya:

Ya Allah! Tolonglah kami supaya ingat kepadaMu, syukur kepadaMu dan elok ibadah kepadaMu.


Ertinya:

Wahai Tuhan kami, janganlah engkau cabut iman kami setelah engkau memberi hidayah kepada kami dan berilah kami rahmat yang ada di sisiMu sesungguhnya Engkau sangat banyak memberi.


Ertinya:

Wahai Tuhan kami, berilah kami kebahagiaan hidup di dunia dan di akhirat dan jauhkan kami daripada azab api neraka. Allah memberi rahmat kepada penghulu kami Muhammad dan ke atas ahlinya dan sahabatnya. Segala puji bagi Allah tuhan seru semua alam.


Ertinya:

Dengan nama Allah yang Maha Pemurah lagi Maha Mengasihani, segala puji adalah semata-mata untuk Allah, Tuhan Pemerintah alam. Selawat dan salam adalah untuk junjungan besar semulia-mulia Nabi dan Rasul dan begitu juga ke atas keluarga serta sahabat baginda kesemuanya.

Ya Allah! Ya Tuhan kami, sesungguhnya kami memohon supaya Engkau memelihara kami. KepadaMulah kami pertaruh agama kami. Ahli keluarga kami, harta-harta kami. Ya Allah, jadikanlah kami dan mereka itu di dalam pemeliharaan, perlindungan, keamanan dan penjagaanMu daripada semua syaitan, dan yang mempunyai mata yang mempunyai kedengkian dan yang mempunyai kejahatan. Sesungguhnya Engkau di atas tiap-tiap sesuatu itu Maha Berkuasa.


Ertinya:

Ya Allah! Perbaikilah perdamaian kami dan lembutkan antara hati-hati kami dan tunjukkan kami jalan keselamatan dan lepaskan kami daripada kegelapan kepada cahaya dan jauhkan kami daripada sebarang kejahatan samada yang terang atau yang tersembunyi. Ya Allah, berkatilah pada pendengaran dan penglihatan kami dan hati-hati kami dan isteri kami dan keturunan kami dan berilah kami taubat, sesungguhnya Engkau Maha Memberi taubat lagi Maha Penyayang dan jadikanlah kami syukur di atas nikmat-nikmatMu, memuji dengan nikmat pemberianMu menerima dengan pemberian akan nikmatMu dan sempurnakan nikmatMu ke atas kami.


Ertinya :

Ya Tuhan kami, kurniakanlah kepada kami kebajikan dunia dan kebajikan di akhirat dan pelihara kami daripada azab neraka. Rahmat Allah ke atas penghulu kita, Nabi Muhammad (S.A.W) ke atas keluarga dan para sahabat semuanya. Maha Suci Tuhanmu, Tuhan yang mempunyai sifat kemuliaan daripada apa yang mereka gambarkan. Dan, kesejahteraan ke atas segala Rasul dan segala puji semata-mata untuk Allah yang memerintah sekelian alam.

Monday, September 01, 2008

first day pose

1/9/2008 bersamaan 1 ramadhan 1429..wah ngantuk nya arini..arini juga merupakan hr first aku keje after 2 weeks tak masuk opis..
br lepas settlekan satu sistem yang down last week..de berlambak lagik keje yang tak siap ni..
pening+ngatuk...letih pon ade..tak sabar rasenye nk berbuka pose..hehehe..

tertido kejap kat opis tadi..lega jap rasanya...