SQLite comes packaged with PHP 5. It has advanced  capabilities  and a built-in object-
oriented (OO) interface. Examining the classes and methods of SQLite is the ostensible
reason  for including this chapter—but that’s not the only reason.  SQLite is a great addition to PHP, but because  MySQL is so entrenched, programmers tend to ignore  SQLite.
Don’t  let the  “Lite” in SQLite  lead  you to underestimate the  capabilities of this database. Because it is bundled with PHP,  there is no external server to worry about—think of it as “Lite” in the  sense  of “no extra  baggage.” In some  situations it is the  ideal database to use. Its advanced features can help simplify your code  and  create an application that  outperforms other solutions.
In this chapter, we will develop a link management application using  a class derived from  the  SQLite  database class. A minimum of PHP version
5.0.5 is a requirement. (Prior to this version  the  SQLite  database class is declared as final, so it cannot be extended.)
Brief Overview
Relevant sections of code  will be reproduced here, but,  as usual,  the  entire application is available  for download on the  companion website.  The  front end  for this application will display alphabetically ordered website links, as shown  in Figure  15-1.
An alphabetic navigation bar of hyperlinks will make  any specific  link easily accessible. Recently added links will be highlighted, making the  list even more useful  to regular visitors.
A submission form  will allow visitors to suggest  additional links. These links will not  appear on the  site until  they have been reviewed.  There will be a back end  to review and  maintain links.
Directory Structure
Because  of the  number of files in the  download for this chapter, it’s helpful to make  a few comments about the  way the  files are organized. Download and  decompress the  files to follow along.
The front-end capabilities of this application are accessible from  the  links in the  index.php file in the  top level directory and  the  back end  is found using the  index.php file in the  linkmanagement directory. On  a production server the linkmanagement directory would  be password protected but for ease of use that hasn’t  been done here.
For reasons of version  compatibility, the database file itself is not  included with the downloads. It should be installed in the dbdir directory. Version  2.8.17 of SQLite  was used  to test this application (but if you are already up and  run- ning  with another version  of SQLite  you shouldn’t run  into  any problems). Install  the database from  the  db_install_script.php file (also included in the dbdir directory). Instructions on how to do this will follow shortly.
How  It’s Done
In this application we take advantage of some  of SQLite’s advanced capa- bilities.  Both  triggers and  views will be used.  A trigger, code  that  executes in response to an add,  edit,  or delete event,  will be used  to mimic  a datestamp field—records will be automatically stamped whenever they are added or changed.
Views are a convenient way of storing queries and  can replace tables  in the  FROM clause  of a SELECT statement. They can also be used  with triggers so that  “updating” a view updates the  associated table.
No database used  in conjunction with PHP can escape comparison to MySQL. Where appropriate, I will point out  differences in SQL syntax between SQLite  and  MySQL. Likewise, SQLite  has a variety of different query methods. These will also be contrasted with MySQL functions.
As you have seen,  throwing exceptions rather than trapping errors makes  for cleaner code.  SQLite  has a built-in OO  interface, and  there is an SQLiteException class. However, only the  SQLite  database constructor throws exceptions. By extending the SQLite  database class, we can override the  query methods so that  a failed  query  also throws an exception. This derived class will also include data  verification methods that  make  use of metadata extracted from  the  database. This will be done by querying the  sqlite_master table  and through the  use of pragmas. A pragma modifies the  way the  SQLite  library works but  can also be used  to query  the database structure. We’re only inter- ested  in the  second use.
A limited number of functions are available  for use with SQLite’s dialect of SQL. You’ll see how this shortcoming can be overcome by creating user- defined functions (UDFs).
Getting Started
SQLite comes  bundled with PHP 5, so all you have to do to install the database is run  the  db_install_script.php file.
However, if you do things this way you’ll have to write code  just to view your data  or to examine the  structure of your database. You might want to download the  command-line version  of SQLite  instead. PHP 5, depending upon the  subversion number, comes  with SQLite  versions  2.8.11 through
2.8.17. To find  out  which version  is running on your system, display the
results  of the  PHP  function phpinfo in your  browser and  search for SQLite. For convenience, you might want to install  the  binary  of sqlite in the  same directory as your database.
Creating a database is as simple  as typing  the  name of the  SQLite executable file at the  command line followed  by the  database name—for example, sqlite  resources.sqlite. Doing  so will run  sqlite and  create or open an existing database of the  specified name. You can  now create a table  using  SQL from  the  command line.  However, let me make  one  more suggestion. At some  point you will want to dump your database, and  if you have created it from  the  command line  the  output won’t be very readable.
If you use a text  editor to format your CREATE  TABLE statement and  then redirect this file to the  database, the  results  will be much more acceptable. Do this whenever you create tables,  views, or triggers.
NOTE 	Precompiled binaries for most operating systems are available at the SQLite download page (http://sqlite.org/download.html). For compatibility reasons it is important to get the command-line version number that matches the version incorporated into PHP. At the SQLite site you may have difficulty finding older versions. If there is no link to the version you require, enter the URL http://sqlite.org, followed by the version number
you require, into the address bar of your browser—for example, http://www.sqlite.org/ sqlite-2_8_16.zip. You might get away with using a slightly higher or lower version number, but version 3 databases are an entirely different format from version 2, so do not attempt to use the version 3 command-line tool with a version 2 database.
The  database used  in this application is called  resources.sqlite and  is stored in a subdirectory named dbdir. If you haven’t already created it using the  db_install_script.php file, you can do so by redirecting the  dump.sql file from  the  command line in the  following  way:
sqlite  resources.sqlite < dump.sql
A database dump is formatted as a transaction, so, if this command worked properly, you’ve already  used  one  of SQLite’s advanced features.
You can test that  the  database has been installed by executing a SELECT statement. Typing  SELECT  *  FROM  tblresources; should display all the  records in the  resources table.
Creating a Table
The  SQL used  to create the  tblresources table  in our  database is shown  in
Listing  15-1.
CREATE  TABLE  tblresources(
id INTEGER   PRIMARY  KEY,
url  VARCHAR(255)  NOT  NULL  UNIQUE   default  '', email  VARCHAR(70)  NOT  NULL   default  '', precedingcopy  VARCHAR(100)  NOT  NULL   default '', linktext  VARCHAR(255)  NOT  NULL   default  '', followingcopy  VARCHAR(255)  NOT  NULL   default '', target  VARCHAR(35)  default '_blank',
category VARCHAR(100)  NOT  NULL   default  '',
theirlinkpage VARCHAR(100)  default  NULL, whenaltered  TIMESTAMP  default '0000-00-00', reviewed  BOOLEAN  default  0,
whenadded  DATE  default '2006-05-05');
Listing 15-1: Creating a table
Let’s have a look at the  details.
To create a table  with an autonumber field named id, the data type INTEGER is used  in conjunction with PRIMARY  KEY. This is equivalent to identifying a field as INTEGER  auto_increment  PRIMARY  KEY in MySQL. In SQLite this field definition is the  one  exception to the  rule  that  SQLite  fields are typeless—otherwise all fields are strings.  Creating fields as types other than string  helps  document the data types you are expecting but will not restrict the value entered. You can put  a string  into  a float type field and  a float into  a Boolean. Further, specify- ing the  length of a VARCHAR type field will not  truncate data  that  exceeds the defined length. Any length of string can be entered into any field. Otherwise, the  syntax for creating a table  functions exactly as you might expect.
The  field names used  in creating this table  are self-documenting, but  a few comments are in order. A resource won’t be displayed until  the  reviewed field is set to true. The  field with the  data  type TIMESTAMP  whenaltered will be maintained using  a trigger as will the  whenadded field.
Views
Views are stored SELECT queries. If you repeatedly use the  same  query,  it is worthwhile creating it as a view.
To make  resource links easily accessible, let’s order them alphabetically and  create hyperlinks to each  letter of the alphabet. With this in mind, take a look  at the  alphabet view shown  in Listing  15-2.
CREATE  VIEW  alphabet  AS
SELECT  DISTINCT   UPPER(SUBSTR(linktext,1,1))  AS  letter
FROM  tblresources
WHERE   reviewed =  1
ORDER   BY  letter;
CREATE  VIEW  specific_link  AS SELECT  id,  url,
(precedingcopy || ' ' || linktext  || ' ' || followingcopy) AS  copy
FROM  tblresources;
Listing 15-2: Views
The  alphabet view creates a row of links as pictured at the  top  of
Figure 15-1.
Rather than repeat the  SQL statement that  makes  up the  alphabet view, we can instead simply SELECT  * FROM  alphabet using the  name of the view in the FROM clause.
The  second view, specific_link, also shown  in Listing  15-2, demonstrates how a view can be “updated” when  used  in conjunction with a trigger. We will return to this view in the  following  discussion about triggers, but do note the use of || as the  string  concatenation operator.
As you can see, SQLite defines its own string  manipulation functions. For a complete list of functions and  operators, see www.sqlite.org/lang_expr.html.
Triggers
For those programmers who pride themselves on their laziness, triggers are a wonderful thing. By creating a trigger you can get maximum effect with mini- mum effort.
Triggers are activated by an INSERT, DELETE, or UPDATE SQL statement. They are often used  to maintain referential integrity and  avoid orphaned records— for example, deleting an invoice  might well trigger deletion of all related invoice  items.  We’re going  to create three triggers for our  application: one to mimic  a timestamp field, another to show the  advantages of laziness,  and finally a trigger to demonstrate how a view can be “updated.”
The  timestamp triggers are shown  in Listing  15-3. They are activated whenever a record in the  tblresources table  is added or updated.
CREATE  TRIGGER   insert_resources  AFTER  INSERT ON  tblresources
BEGIN
UPDATE  tblresources SET whenaltered  =  DATETIME('NOW','LOCALTIME') WHERE   id =  new.id;
END;
CREATE  TRIGGER   update_resources  AFTER   UPDATE   ON  tblresources
BEGIN
UPDATE  tblresources SET whenaltered  =  DATETIME('NOW','LOCALTIME') WHERE   id =  new.id;
END;
CREATE  TRIGGER   add_date AFTER  INSERT   ON tblresources
BEGIN
UPDATE  tblresources SET whenadded  = DATE('NOW','LOCALTIME') WHERE   id =  new.id;
END;
CREATE  TRIGGER   delete_link  INSTEAD   OF  DELETE   ON  specific_link
FOR   EACH  ROW BEGIN
DELETE   FROM  tblresources
WHERE   id =  old.id; END;
Listing 15-3: Triggers
There is no need to remember to update the  whenaltered field each  time a change is made to a record—the insert_resources  and  update_resources triggers will do this for you. The  current date  and  time  will be added in the background. Effectively, this field will now function like a MYSQL TIMESTAMP field.
Likewise with the  add_date trigger, also shown in Listing  15-3. We want to highlight new links. This trigger makes  it possible to capture the  date  a link is added. By using  a trigger we don’t have to worry about forgetting to main- tain  this field, and  we don’t have to write additional code  each  time  a record is added.
Creating a trigger on a view is a convenient way of performing an “update” against a view. By themselves, views are not  updatable. If you attempt to delete from  a view that  has no  associated trigger, you’ll get a warning like the following:
Warning:  SQLiteDatabase::query()  [function.query]: cannot modify  specific_link  because  it is a  view...
We solved this problem in the  trigger we created on the view specific_link shown  in Listing  15-3. Because  we used  an INSTEAD   OF clause,  any attempt to delete from  this view instead removes the  appropriate record from  the table, tblresources.
In this trigger we have specified FOR   EACH  ROW. Doing  so is optional. A FOR   EACH  STATEMENT clause  also exists but  is not  yet supported.
The  WHERE  clause  of a trigger is somewhat intuitive but  may cause  some confusion. Using new.id to specify a newly inserted record and  old.id for a deleted record clearly makes  sense.  Either old or new may be used  when  a record is updated.
Using  triggers is very convenient, although the  same  effect  could be achieved programmatically. But because triggers are embedded in the  data- base, they are activated even when  you make  changes from  the  command line.  Triggers help maintain the integrity of your database when it is modified outside of your application. Laziness  has its rewards.
PHP Implementation of SQLite
For the  most part, the  OO  methods of SQLite  are exactly the  same as the procedural functions. The  only difference is that  the  leading sqlite is dropped and  the  studly caps naming convention is used  in place  of under- scores  (although some  methods added in version  5.1 don’t quite follow this rule). Method parameters are  the  same  as those used  with the  procedural functions, except that  there is no  need to pass a resource handle since  the object itself is the  handle. A few functions are only available  in a procedural form;  these will be mentioned where  appropriate.
There are three built-in, ready-to-use SQLite objects:  an SQLite database, a buffered result  set, and  an unbuffered result  set. All three classes will be used in this chapter, but  the  focus will be on the  database class.
Extending SQLiteDatabase
One of the  nice  things about object-oriented programming (OOP) is excep- tion  handling. Procedural error trapping is not  only tedious, it clutters up your code  and  can make  it unreadable. Taking an OO  approach and  using exception handling sounds like the  ideal  solution—until you realize  that  the database constructor is the  only method of all the  SQLite  classes that  throws
an exception. If you want to check  for errors when  creating result  sets, you are stuck using  procedural code. It looks like we’re right  back where  we started.
We’ll next  discuss how this can be fixed.
Override the  Query Methods
The  simple  solution to this problem is inheritance. On  the  surface, this would  seem  fairly straightforward: Create a class that  extends SQLiteDatabase and  override all the  query  methods. If errors arise within  those overridden methods, simply throw  an exception. In this way, the  messy details  of error trapping can be buried inside  the class file and  a single  catch block can handle all errors. The first five methods in the class definition file shown in Listing 15-4 do exactly this.
Error Messages
The  comment immediately inside  each  method definition shows the  method prototype as defined on the PHP site. This is especially  useful because it shows the  type of object  returned. Some  of the  base class methods take an optional string  reference argument (&$error_msg).
NOTE 	In versions of PHP prior to 5.1, passing in this string reference results in this warning:
SQLiteDatabase::query()  expects at  most  2  parameters,  3  given.
The  reason a third parameter is necessary is explained as follows (from http://php.net/sqlite_query):
... [$error_msg] will be filled if an error occurs.
This is especially important because SQL syntax  errors can’t  be fetched using  the  [sqlite_last_error()] function.
Quite true. The  sqlite_last_error function returns an uninformative message:  SQL  logic  error or  missing database. Our  code  doesn’t make  use of this error message  but  this isn’t an insurmountable problem. A more
specific  error message would  certainly help  in the  debugging process, how- ever. Fortunately, if you have warnings turned  on while you are developing,
you will get something more meaningful. Forcing a warning by referencing a nonexistent table  results  in the  following, more specific,  output:
Warning:  SQLiteDatabase::query()[function.query]:  no  such table:  tblnonexistent...
Query Methods
Look  again  at Listing  15-4. It includes the  five methods for creating result sets. The  buffered and  unbuffered methods are fairly self-explanatory—you are probably quite familiar with the  equivalent MySQL functions. However, MySQL (prior to the  MySQL improved extension) has nothing to match the singleQuery, queryExec, or arrayQuery methods. Let’s look at these  methods in more detail.
The  singleQuery method is a recent addition, and  the  PHP site warns that it is not  currently documented. Let’s carry on regardless because this method looks especially  useful  for those situations where  a query  returns only one row—when using  the  COUNT function to return the  number of records in a table,  for example. Here’s one  view of how this method ought to behave: This method returns only one  record, and  no  result set is created. If the  second argument is false, the  value returned is an array of the  first row. If the  sec- ond  argument is true, then only the  first column of the  first row is returned, and  it is returned as a scalar  value.
This speculation may make  the  best sense  of how this method ought to work, but  it doesn’t describe what actually happens. In fact, this method only ever returns the  first column and  any number of rows. If the second argument is false, then an array is returned; if the second argument is true and  only one row is returned, a scalar is returned. On  the  PHP site, this second argument is identified as bool  first_row_only and  the return type is identified as an array. It looks like the  return type should be mixed. In any case, this method doesn’t yet work the  way it ought to. We were warned.
There is no requirement that  you use the  singleQuery method instead
of query. As with MySQL, you can always create a result  set and  then use the appropriate fetch function to retrieve the  value of the  first row or a specific field. But why return an object or an array when  all that’s needed is the  value of one  column? You may use the  singleQuery method for any kind  of query— data  manipulation or otherwise—but it was designed specifically  for situa- tions  where  a single value or single  column is returned, and  is presumably optimized for this situation.
As you can see, there is also an arrayQuery method. Like the  singleQuery method, this method allows us to directly  copy results  into  an array, bypassing the  intermediate step of creating a result set. This method is best used  when a limited number of records are returned.
MySQL versions prior to 4.1 have no equivalent to the queryExec method of SQLite  because queryExec is specifically designed for use with multiple queries. Multiple, semicolon-separated queries may be passed  as a single query string  to queryExec. (The install script  uses this method to create the tables, triggers, and views and  to insert records into  the  tblresources table.) This method gives significant performance improvements over repeated querying and  performs
the same job as the  MySQL-improved (the mysqli functions added to PHP 5 to support MySQL 4.1) method, mysqli_multi_query. If you like, you can of course use this method to execute a single query.
query
Use of this method to create an SQLiteResult object is shown  in Listing  15-5.
$db  = new SQLiteDatabasePlus('resources.sqlite');
//alphabet  view
$strsql  = "SELECT  *  FROM  alphabet";
//use  buffered result  set to get  number  of rows
$result  = $db->query($strsql);
//create  alphabet  here if($result->numRows()  > 0){
echo  get_alphabet($result);
}
Listing 15-5: query method returns a buffered result set
Remember, an SQLiteResult is buffered so you can use the  numRows method with this result  set. It is also iterable, so this result  set may be used  in a foreach loop.  In this, SQLite  differs  from  MySQL. Because  SQLiteResult implements Iterator, all the iterator methods are present—rewind, next, valid, and  current. These methods can  be used  directly, but  their real  purpose is to allow an SQLite  result  set to be used  in a foreach loop  in exactly the  same way that  you might use an array. (As you might expect, the  rewind method can’t be applied to an unbuffered result  set.) Only this method and  the  unbuffered query method return a result  set object.
unbufferedQuery
There is no need to buffer the  result  set returned in Listing  15-6.
try{
$db  = new  SQLiteDatabasePlus('../dbdir/resources.sqlite');
$type="Edit";
//retrieve  from  db
$strsql  = "SELECT  *  FROM  tblresources ". "WHERE  id = '$id'";
//get  recordset as  row
$result  = $db->unbufferedQuery($strsql);
$row = $result->fetch();
//can't  use  below  because   returns first column only
//$row  = $db->singleQuery($strsql,  false);
// assume  vars same as  fields while(list($var,  $val)=each($row))  {
$$var=$val;
}
}catch(SQLiteException  $e){
//debug  msg
echo    $e->getMessage();
}
}
Listing 15-6: The unbufferedQuery method
This listing  shows an unbuffered query.  In this case, a functional singleQuery method would  be preferable because we know that  only one record will be returned. However, given the  problems with singleQuery, we use the  unbufferedQuery method of an SQLiteDatabase object to create a result set object and  then use the  fetch method to copy the  first row into  an array.
arrayQuery
The  PHP site warns against using  the  arrayQuery method with queries that return more than 45 records (a somewhat arbitrary number perhaps, but this method stores  results  in memory so returning a large  number of records can exhaust memory). We’ve used  this method in Listing  15-7.
...
$db  = new SQLiteDatabasePlus('../dbdir/resources.sqlite');
$db->createFunction('class_id','set_class_id',0);
$sql =  "SELECT  id, url,  email,  ".
"(precedingcopy || ' ' || linktext  || ' ' || followingcopy)  ". "AS  copy,  linktext,  reviewed,  class_id() AS  classid ".
"FROM  tblresources ". "ORDER  BY   id  DESC  ".
"LIMIT  $recordoffset,".  PERPAGE;
//use  arrayQuery
$resultarray = $db->arrayQuery($sql);
Listing 15-7: Using arrayQuery
As you can see, we know exactly how many records are returned because our  SQL has a LIMIT clause.  Again, this method allows us to bypass creation of a result  set.
singleQuery
The  code  below uses the  singleQuery method and  does  exactly what we need—it returns a single  scalar value rather than a result  set or an array.
$totalrecords = $db->singleQuery('Select  COUNT(*)  FROM
tblresources',  true);
queryExec
This method is commonly used  to process a transaction. Use the  command- line command .dump to dump your database or view the  file dump.sql. You’ll see that  it is formatted as a transaction. You can recreate an entire database by passing  this listing as a string  to the  queryExec method, as we have done with the  install  script,  db_install_script.php.
The  ability to perform multiple queries using  one  string  does  raise security  issues. When  using  this query  method, it is especially  important to filter data  in order to avoid a possible SQL injection attack.
Utility Methods
By overriding all the  query  methods of the  SQLiteDatabase class we ensure that any failed query  throws an exception. This done, we needn’t worry about error trapping whenever we perform a query. The remaining methods of our derived class are utility methods aimed at helping verify data  posted from  a form. These methods give us an opportunity to explore some  of the ways to retrieve metadata from  an SQLite database. Find  those methods in Listing  15-8.
/**
Get  all  table names  in database
*/
public  function getTableNames(){
if (!isset($this->tablenames)){
$this->setTablenames();
}
return  $this->tablenames;
}
/////////////////////////////////////////////////////////////
/**
Retrieve  field names/types  for specified  table
*/
public  function getFields($tablename){
if (!isset($this->tablenames)){
$this->setTablenames();
}
if (!in_array($tablename,  $this->tablenames)){
throw  new SQLiteException("Table  $tablename not  in  database.");
}
$fieldnames  =  array();
$sql  =  "PRAGMA  table_info('$tablename')";
$result  =  $this->unbufferedQuery($sql);
//no  error - bad  pragma  ignored
//get  name and  data type as defined upon  creation foreach  ($result  as $row){
$fieldnames[$row['name']]  = $row['type'];
}
return  $fieldnames;
}
//////////////////////////////////////////////////////////////
//private  methods
/**
private method  - initializes  table names  array
*/
private function   setTableNames(){
$sql  = "SELECT  name ". "FROM  sqlite_master  ". "WHERE  type  = 'table' ". "OR  type = 'view'";
$result  = $this->unbufferedQuery($sql);
foreach    ($result  as  $row){
$this->tablenames[]  = $row['name'];
}
}
Listing 15-8: Metadata methods
The  two methods that  make  use of metadata are setTableNames and getFieldNames. Let’s examine     the method setTableNames in Listing 15-8. This method makes use of the table  sqlite_master—a table  that  defines the schema for the  database. By querying sqlite_master, we can retrieve the  names of all the tables  and  views in the  database. The  type field defines the  kind  of resource, in our  case a table  or view. This method retrieves the  names of all the  tables  and  views and  stores  them in an array.
Ideally,  this method would  be called  once from  the  constructor, but the  constructor for an SQLite  database is declared final, so it may not  be overridden.
Pragmas perform a variety of functions in SQLite.  One of those func- tions  is to provide information about the  database schema—about indices, foreign keys, and  tables.
Running the  pragma table_info returns a result  set that  contains the column name and  data  type. The  data  type returned is the  data  type used when  the  table  was created. This may seem  pointless—since, excepting one case, all fields are strings—but this information could be used  to assist data validation. For example, with access to a data  type description, we could programmatically enforce which  values are allowed  for which  fields. Notice that  the  pragma table_info can also be used  with views. However, when  used with views, all field types default to numeric.
A word of warning about pragmas: They fail quietly,  issuing  no warning or error, and  there is no guarantee of forward compatibility with newer  versions of SQLite.
Getting Metadata
Metadata methods allow us to discover  field names at runtime. This is useful when  we want to match posted values to the  appropriate field in a table.  Fig- ure  15-2 shows the  form  that  we will use to post data  to the  database.
As you can see in Listing  15-9,   the  cleanData method verifies that  the keys of the  posted array match table  field names by calling      the  matchNames method. It throws  an exception if they don’t. However, it also removes slashes if magic  quotes are on.  If you regularly use MySQL with magic  quotes on, escaping data  may be something you never  give much thought to. However, unlike MySQL, SQLite  does  not  escape characters by using  a backslash; you must  use the  sqlite_escape_string function instead. There is no OO  method for doing this.
There is no requirement to call the  cleanData method, and  there may be situations where  its use is not  appropriate—perhaps where  security  is a prime concern, so naming form  controls with table  field names is not  advisable. How- ever, it is a convenient way of confirming that  the  right value is assigned to the  right  field.
User-Defined Functions
One of the  requirements of our  application is to highlight recently added links. We are  going  to achieve  this effect  by using  a different CSS class for links that  have been added within  the  last two weeks. Subtracting the  value stored in the  whenadded field from  the  current date  will determine the  links that  satisfy this criterion. Were we to attempt this task using MySQL, we could add  the  following  to a SELECT statement:
IF(whenadded  >  SUBDATE(CURDATE(),INTERVAL  '14'  DAY),  'new',
'old')  AS  cssclass
This would create a field aliased  as cssclass that  has a value of either new or old.  This field identifies the  class of the  anchor tag in order to change its appearance using  CSS. It’s much tidier to perform this operation using  SQL rather than by manipulating the  whenadded field from  PHP  each  time  we retrieve a row.
But SQLite  has no  date  subtraction function. In fact, the  SQLite  site doesn’t document any date  functions whatsoever. Does this mean that  we are stuck retrieving the  whenadded field from  the database and  then performing the date  operations using  PHP? Well, yes and  no.  SQLite  allows for user-defined functions (UDFs). Let’s take  a look at how this works.
The  first thing to do is create a function in PHP to subtract dates—not a terribly difficult task. See the  function check_when_added in Listing  15-10 for the implementation.
function  check_when_added($whenadded){
//less than 2  weeks  old
$type = 'old';
// use  date_default_timezone_set    if available
$diff  = floor(abs(strtotime('now')  - strtotime($whenadded))/86400);
if($diff  < 15){
$type  = 'new';
}
}
...
...
return  $type;
//register  function
$db->    createFunction('cssclass','check_when_added',1);
$strsql ="SELECT  url,  precedingcopy, linktext,  followingcopy,  ". "UPPER(SUBSTR(linktext,1,1))  AS  letter, ". "cssclass(whenadded) AS  type,  target  ".
"FROM  tblresources ". "WHERE  reviewed  = 1  ". "ORDER  BY   letter ";
$result  = $db->query($strsql);
Listing 15-10: A user-defined function
Also shown  in Listing  15-10 is     the  createFunction method of an SQLite- Database, which  is used  to make  check_when_added available  from  SQLite. Calling  this function is as simple  as adding the expression cssclass(whenadded) AS  type to our  SELECT statement. Doing  this means that  the  result set will
contain a field called  type with either a value of new or no  value at all. We can  use this value as the  class identifier for each  resource anchor tag. The new anchors can be highlighted by assigning them different CSS display
characteristics.
The  back end  of our  application also makes  use of a UDF; improved readability is the  motivation behind its creation.
The  set_class_id function in Listing  15-11 (  ) shows how the  mod operator can be used  in a UDF to return alternate values. When  this value is used  as the  id attribute for a tr tag, text  can  be alternately shaded and unshaded by setting the  style characteristics for table  rows with the  id set to shaded. Again, it is much tidier to return a value in our  result  set rather than to perform this operation from  PHP. Once you are familiar with UDFs you’ll see more and  more opportunities for using  them. Be careful. Using  them can become addictive.
//add  function  to  SQLite function   set_class_id(){
static  $x = 0;
$class = 'unshaded';
if(($x  %   2)  == 0){
$class  = "shaded";
}
...
}
$x++;
return  $class;
$db = new SQLiteDatabasePlus('../dbdir/resources.sqlite');
$db->createFunction('class_id','set_class_id',0);
Listing 15-11: UDF shades  alternate rows
You can’t  permanently add  a UDF to a database, but  the  ability to create them certainly compensates for the  limited number of functions in SQLite, especially  those related to date  manipulation. In fact, in my view, this way of subtracting dates  is much easier  to implement because it doesn’t involve looking up or remembering the quirky syntax of functions such as the  MySQL SUBDATE  function referenced earlier. However, UDFs lack the  performance benefits of built-in functions.
Uses and Limitations of SQLite
No database can be all things to all people. SQLite  supports any number of simultaneous readers, but a write operation locks the entire database. Version  3 offers some  improvement in this respect, but  SQLite  is still best used  in appli- cations with infrequent database updates, like the  one  described here.
Of course, if access control is important, then SQLite  is not  the  appropri- ate tool. Because  GRANT and  REVOKE are not  implemented, there can  be no
database-enforced user restrictions.
However, even a relatively  modest application can make  use of the advanced capabilities of SQLite.  With the  application discussed in this chapter, we haven’t had  to sacrifice  anything by using  SQLite  rather than MySQL. Unavailability of a timestamp field is remedied by use of a trigger. A UDF makes  up for SQLite’s lack of date  manipulation functions. In fact,
overall,  we achieve  better performance because there is no overhead incurred by a database server,  and  maintenance is reduced through the  use of triggers.
Not only has using  SQLite  simplified our  code  through the  use of views, triggers, and  UDFs, as well as by extending the OO interface, but it also makes for cleaner code  through its more varied ways of querying a database. In these or similar  circumstances, SQLite  is definitely a superior choice.
Wednesday, August 12, 2009
EXTENDING SQLITE
Posted by Mr Procces at 6:16 AM
Labels: PHP, PHP Programming, Web Applications, Web Development
Subscribe to:
Post Comments (Atom)
0 comments:
Post a Comment