Working with the ORM Installer
One of the more tedious elements of any application is managing model object classes. Fortunately, the Istarel Workshop Application Framework has a command line ORM tool that automatically creates (or updates) model objects based on the database schema.
It has a number of switches and options to fine-tune any updates. The simplest version of the command:
cd ~/Sites/fw/install php orm_install -n Istarel Workshop -w /Users/markf/Sites -a iw -r article
This tells the installer to create the model object classes for the article relation in the database used by the iw application. It uses [u]/Users/markf/Sites/iw/conf/ApplicationConstants.php[/u] to connect to the database and retrieve the schema information for the article relation (table). The default parent model object classes are created or updated: [u]/rsrc/model/default/DefaultArticle.php and /rsrc/model/factory/DefaultArticleFactory.php[/u]. The child model object classes, which are the classes developers should use, are only created if not already present (Article is a subclass of DefaultArticle, and ArticleFactory is a subclass of DefaultArticleFactory).
Listing: DefaultArticle.php
class DefaultArticle extends ORMObject{ // Properties representing table columns public $article_id; public $author_id; public $title; public $publish_date; public $ready_to_publish; public $public_path; public $article; public $content; // Factory function factoryClass( ) { return 'ArticleFactory'; } // Many Relations function articleCategories( $requestor= null) { $select= new ORMSelect( 'article_category') ; $select-> addCriteria( new ORMCriterion( 'article_id', $this-> article_id) ) ; return ArticleCategoryFactory:: factory( ) -> retrieveObjects( $requestor, 'ArticleCategory', $select) ; } // Validation function validateAuthorId( & amp; $err) { if( $this-> invalidForRule( 'IWRequiredRule', $this-> author_id) ) $err[ ] = IWDatabase:: ERROR_IS_NULL; return empty( $err) ; } function validateTitle( & amp; $err) { if( $this-> invalidForRule( 'IWRequiredRule', $this-> title) ) $err[ ] = IWDatabase:: ERROR_IS_NULL; if( $this-> invalidForRule( 'IWLengthRule', $this-> title, [ 0, 100] ) ) $err[ ] = IWDatabase:: ERROR_TOO_LONG; return empty( $err) ; } function validatePublishDate( & amp; $err) { if( $this-> invalidForRule( 'IWRequiredRule', $this-> publish_date) ) $err[ ] = IWDatabase:: ERROR_IS_NULL; return empty( $err) ; } function validatePublicPath( & amp; $err) { if( $this-> invalidForRule( 'IWLengthRule', $this-> public_path, [ 0, 200] ) ) $err[ ] = IWDatabase:: ERROR_TOO_LONG; if( ! $this-> verifyPropertyIsUnique( 'public_path') ) $err[ ] = IWDatabase:: ERROR_NOT_UNIQUE; return empty( $err) ; } function validateArticle( & amp; $err) { if( $this-> invalidForRule( 'IWRequiredRule', $this-> article) ) $err[ ] = IWDatabase:: ERROR_IS_NULL; return empty( $err) ; } }
Listing: DefaultArticleFactory.php
class DefaultArticleFactory extends ORMFactory{ function factory( ) { return IWFactory:: defaultFactory( 'ArticleFactory') ; } function retrieveArticle( $property_value= null, $property_name= null, $properties= ORMObject:: ALL_PROPERTIES) { return ORMFactory:: retrieveObject( 'Article', $property_value, $property_name, $properties) ; } function articles( $requestor= null) { return ArticleFactory:: factory( ) -> retrieveObjects( $requestor, 'Article') ; } function instanceClass( ) { return 'Article'; } function sourceName( ) { return 'article'; } function identifierProperty( ) { return 'article_id'; } function primaryKeySequence( ) { return 'article_seq'; } function propertyType( ) { return array( 'article_id'=> ORMPostgreSQLAdapter:: PGSQL_INT4, 'author_id'=> ORMPostgreSQLAdapter:: PGSQL_INT4, 'title'=> ORMPostgreSQLAdapter:: PGSQL_VARCHAR, 'publish_date'=> ORMPostgreSQLAdapter:: PGSQL_DATE, 'ready_to_publish'=> ORMPostgreSQLAdapter:: PGSQL_BOOLEAN, 'public_path'=> ORMPostgreSQLAdapter:: PGSQL_VARCHAR, 'article'=> ORMPostgreSQLAdapter:: PGSQL_TEXT, 'content'=> ORMPostgreSQLAdapter:: PGSQL_TEXT) ; } function noCopyProperties( ) { return array( 'article_id') ; } }
I left off comment blocks at the top of each file which define the class (DefaultArticle or DefaultArticleFactory) and project (Istarel Workshop).
Note that the automatically generated validation methods apply any constraints defined in the database schema. For example, there is a unique constraint on the public_path column of the article table, so that is reflected in the validatePublicPath()
method. Likewise, any varchar columns have a IWLengthRule applied in their validation method.
Accessor Methods
If you want to have accessor methods automatically written, you include the -p or --with-property-methods switch.
cd ~/Sites/fw/install php orm_install -p -n Istarel Workshop -w /Users/markf/Sites -a iw -r article
This creates accessor pairs which look like:
function setTitle( $title) { $this-> setValueForProperty( 'title', $title) ; return $this; } function title( ) { return $this-> title; }
Using setTitle($title)
or setValueForProperty('title', $title)
ensures that the model object knows that it has changed such that when its save() method is invoked, an UPDATE statement gets called. If you directly reference $article->title to change the property value, the model object will not be aware it changed. Why use direct reference at all? It's a more efficient approach when you are updating properties in a situation where you know you will not be saving those changes (like when preparing data for a list in an editor).
I always use this switch in my applications.
Deletion Control
If you want a delete() method call to properly delete downstream objects (as is often the case when there is a foreign key relation to the current table), you can use the -d or --use-deletion-control switch.
cd ~/Sites/fw/install php orm_install -d -p -n Istarel Workshop -w /Users/markf/Sites -a iw -r article
The deletion control works by creating a willDelete() method that applies the recipe design pattern to manage deletion of the individual related table records. For article (which has a to-many relationship to article_category) the deletion control section is:
function willDelete( ) { $this-> deleteArticleCategories( ) ; } function deleteArticleCategories( ) { $delete= new ORMDelete( 'article_category') ; $delete-> addCriteria( new ORMCriterion( 'article_id', $this-> article_id) ) ; $delete-> execute( ) ; }
By using the recipe design pattern, the developer model object class (Article) can override willDelete() to replace the entire deletion mechanism, or override the individual deletion methods to apply application-specific needs only to part of the standardized deletion process.
I often use this switch in my applications, but not for every table.
Multiple Tables
The ORM installer is designed for convenience. You can target multiple tables: simply space separate the tables you want to update.
cd ~/Sites/fw/install php orm_install -d -p -n Istarel Workshop -w /Users/markf/Sites -a iw -r article category
You can target all the database tables using the -t or --with-tables-only switch.
cd ~/Sites/fw/install php orm_install -d -p -t -n Istarel Workshop -w /Users/markf/Sites -a iw php orm_install -d -p -n Istarel Workshop -w /Users/markf/Sites -a iw --with-tables-only
Future articles will discuss specific model object needs beyond the scope of the ORM installer.