Prepare PostgreSQL and PHP

Ubuntu uses aptitude for package management. Before attempting any software installations, aptitude should be updated and upgraded (this process can take quite some time).

iwuser$ sudo apt-get update
iwuser$ sudo apt-get upgrade

Install and Configure PostgreSQL

iwuser$ sudo apt-get install postgresql

I want to change the access control to trust for the unix socket connections.

Listing: /etc/postgresql/8.3/main/pg_hba.conf

# Database administrative login by UNIX sockets
local   all         postgres                          trust

# TYPE  DATABASE    USER        CIDR-ADDRESS          METHOD

# "local" is for Unix domain socket connections only
local   all         all                               trust
# IPv4 local connections: (was "md5")
host    all         all         127.0.0.1/32          trust
# IPv6 local connections:
host    all         all         ::1/128               md5

Those changes make it more convenient for using PostgreSQL's command line tool, allowing a user to assume the identity of any PostgreSQL user. The security here is fine because the only way to access PostgreSQL at the command line is to be ssh'd into www.istarelworkshop.com. Any change to PostgreSQL's security requires a restart of the database server.

iwuser$ sudo /etc/init.d/postgresql-8.3 restart
iwuser$ psql template1 postgres
Welcome to psql 8.3.11, the PostgreSQL interactive terminal.

Type:  copyright for distribution terms
       h for help with SQL commands
       ? for help with psql commands
       g or terminate with semicolon to execute query
       q to quit

template1=# q

Install and Configure PHP 5

iwuser$ aptitude search php5-
iwuser$ sudo apt-get install php5-pgsql php5-xdebug

The aptitude search command is useful for seeing what packages are available (the search term is wildcarded). Because this is a production server, I want to modify the PHP configuration file to provide a little more memory to running scripts, and to hide errors from any visitor.

Partial Listing: /etc/php5/apache2/php.ini

memory_limit = 64M      ; Maximum amount of memory a script may consume (16MB)
display_errors = Off

As always, any changes to the PHP configuration require a restart of the Apache server (which is pre-installed).

iwuser$ sudo /etc/init.d/apache2 restart

Smarter Model Object Classes

Absent any special switches used when creating the model object classes, there are two standard methods used as accessors for instance properties: valueForProperty('property_name') and setValueForProperty('property_name', $value).

While versatile, constantly having to write long accessor names becomes tedious very quickly. Fortunately, there is a switch called --with-property-methods available (which can be abbreviated -p) to create developer-friendly method names to access the model object properties.

markf$ cd ~/Sites/fw/install
markf$ ./orm_install -p -n Istarel Workshop -v 3.0 -w ~/Sites -a iw -r category
Initializing Application...
Parsing Tables in iw...
  Analyzing category...

Partial Listing: /iw/rsrc/model/default/DefaultCategory.php

<?php

class DefaultCategory extends ORMObject
{
   ...
   
// @section Accessors
   
   function setCategoryId($category_id)
   {
      $this->setValueForProperty('category_id', $category_id);
      return $this;
   }
   
   function categoryId()
   {
      return $this->category_id;
   }
   
   function setName($name)
   {
      $this->setValueForProperty('name', $name);
      return $this;
   }
   
   function name()
   {
      return $this->name;
   }
   
   function setPublicPath($public_path)
   {
      $this->setValueForProperty('public_path', $public_path);
      return $this;
   }
   
   function publicPath()
   {
      return $this->public_path;
   }
   
   ...

?>

Model Object Classes

The Object-relational Mapping (ORM) tool in the Istarel Workshop Application Framework has a number of switches that affect the way model object classes are constructed. Consider a simple table for categories (used to characterize articles on this blog).

iw=> \d category
                            Table "public.category"
   Column    |         Type          |                     Modifiers                      
-------------+-----------------------+--------------------------------------------
 category_id | integer               | not null default nextval('category_seq')
 name        | character varying(25) | not null
 public_path | character varying(35) | not null
Indexes:
    "pkey_category" PRIMARY KEY, btree (category_id)
Referenced by:
    TABLE "article_category" CONSTRAINT "fkey_category" 
          FOREIGN KEY (category_id) REFERENCES category(category_id)

Here is the simplest use of the Object-relational Mapping (ORM) tool:

markf$ cd ~/Sites/fw/install
markf$ ./orm_install -n Istarel Workshop -v 3.0 -w ~/Sites -a iw -r category
Initializing Application...
Parsing Tables in iw...
  Analyzing category...

Default Object Class

For any table in the underlying database, two default classes are created: one contains the default properties and behavior for an individual instance of that class (representing a row in the table), and one contains metadata relevant to all members of that class.

Listing: /iw/rsrc/model/default/DefaultCategory.php

<?php

class DefaultCategory extends ORMObject
{
   // Properties representing table columns
   public $category_id;
   public $name;
   public $public_path;
   
// @section Factory

   function factoryClass() { return 'CategoryFactory'; }

// @section Many Relations
	
   function articleCategories($requestor)
   {
      $select = new ORMSelect('article_category');
      $select->addCriteria(new ORMCriterion('category_id', $this->category_id));
      $this->restrictQuery($requestor, $select, 'ArticleCategory');
      return ORMFactory::instanceArray('ArticleCategory', $select);
   }

// @section Validation

   function validateName()
   {
      $error = false;
      if (! $this->applyRule('IWRequiredRule', 'name'))
         $error[] = IWDatabase::ERROR_IS_NULL;
      if (! $this->applyRule('IWLengthRule', 'name', new IWRange(0, 25)))
         $error[] = IWDatabase::ERROR_TOO_LONG;
      if ($error) return $this->errorMessage('name', $error);
   }
   
   function validatePublicPath()
   {
      $error = false;
      if (! $this->applyRule('IWRequiredRule', 'public_path'))
         $error[] = IWDatabase::ERROR_IS_NULL;
      if (! $this->applyRule('IWLengthRule', 'public_path', new IWRange(0, 35)))
         $error[] = IWDatabase::ERROR_TOO_LONG;
      if ($error) return $this->errorMessage('public_path', $error);
   }
}

?>

Default classes are intended to not be touched by developers. If the database schema changes, the ORM tool can be run again, modifying the default classes. For each default classes, there is a shell subclass created (in this case, Category) intended for use by developers.

Default Factory Class

For each database table, a default factory class is created that provides metadata to aid in the instantiation of individual objects that represent a row in the database table. From an application developer perspective, the factory class is the place to put methods that result in objects (such as an array of objects meeting certain criteria).

Listing: /iw/rsrc/model/default/DefaultCategory.php

<?php

class DefaultCategoryFactory extends ORMFactory
{
   function createCategoryFactory()
   {
      return parent::createORMFactory('CategoryFactory');
   }
   
   function retrieveCategory($property_value = null,
                             $property_name = null, 
                             $properties = ORMObject::ALL_PROPERTIES)
   {
      return ORMFactory::retrieveObject('Category', $property_value, 
                                                    $property_name,
                                                    $properties);
   }
   
   function instanceClass() { return 'Category'; }
   
   function sourceName() { return 'category'; }
   
   function primaryKeyProperty() { return 'category_id'; }
   
   function primaryKeySequence() { return 'category_seq'; }
   
   function propertyType()
   {
      return array(
   	     'category_id' => ORMPostgreSQLAdapter::PGSQL_INT4,
   	     'name'        => ORMPostgreSQLAdapter::PGSQL_VARCHAR,
   	     'public_path' => ORMPostgreSQLAdapter::PGSQL_VARCHAR
      );
   }
   
   function noCopyProperties() { return array('category_id'); }
}

?>