Over the past few months I’ve been working on a large application powered by my vBulletin MVC framework which required me to make several improvements. One of the models I was working with had about 40 fields, and that caused me to generate all the forms dynamically from model variables dedicated to defining forms. 600 lines of configuration per model later, I grew pretty fed up.

After looking around at other frameworks for fun, I stumbled upon Symfony’s use of Propel, which is a code generator for database access. That got me thinking… At least for initial development, 80% of the work is setting up the initial models, controllers and database tables. Making changes to these are also very tedious, so that inspired me to dig a little deeper and see what I could automate.

After a few hours of tinkering, I managed to get a working copy running (still have to do table schema, but that’s easy). Based on an application configure file, it generates the models, form definitions including validation, controllers, and even action code for index (paginated), view, add and edit. Fully working.

Since we are human and never get anything right the first time, code not in isolated files are wrapped in special comment tags so they can be manually edited without losing the ability to regenerate the dynamic sections.

Examples below. SVN has been updated with the latest code, minus the full generator code as it’s still in development, but the templates are there. Still unstable.

Application Config XML

Code: (Plain Text)
  1. <?xml version="1.0"?>
  2. <application id="product_id">
  3.     <data>
  4.         <table name="user">
  5.             <columns>
  6.                 <column name="userid" auto="true"/>
  7.                 <column name="username" title="Username">
  8.                     <datatype>varchar(255)</datatype>
  9.                     <validation type="string">min(3)</validation>
  10.                     <description>The username of the surgeon</description>
  11.                 </column>
  12.                 <column name="password" title="Password">
  13.                     <datatype>varchar(32)</datatype>
  14.                     <validation type="string">min(3)</validation>
  15.                     <description>Your password</description>
  16.                 </column>
  17.                 <column name="email">
  18.                     <datatype>varchar(45)</datatype>
  19.                     <validation type="string">call(’verify_email’)</validation>
  20.                     <description>Your email address.</description>
  21.                 </column>
  22.                 <column name="fullname">
  23.                     <datatype>varchar(75)</datatype>
  24.                     <validation type="string">min(5)-&gt;no_html()</validation>
  25.                     <description>Your full name.</description>
  26.                 </column>
  27.                 <column name="phone">
  28.                     <datatype>varchar(32)</datatype>
  29.                     <validation type="string">min(5)-&gt;no_html()</validation>
  30.                     <description>Your phone numjber.</description>
  31.                 </column>
  32.             </columns>
  33.         </table>
  34.     </data>
  35.     <!– with the following forms ready for generation –>
  36.     <forms>
  37.         <form id="new-user" table="user">
  38.             <group name="Primary Account Information">
  39.                 <field name="username"/>
  40.                 <field name="password"/>
  41.                 <field name="password" confirm="true"/>
  42.                 <field name="email"/>
  43.             </group>
  44.             <group name="Member Profile">
  45.                 <field name="fullname"/>
  46.                 <field name="phone"/>
  47.             </group>
  48.         </form>
  49.     </forms>
  50. </application>

Model

Code: (Plain Text)
  1. <?php
  2.  
  3. class FW_Model_User extends FW_Model_App
  4. {
  5.  
  6. /**+~~ genStart ~~+**/
  7.  
  8.     var $table = ‘user’;
  9.  
  10.     var $key = ‘id’;
  11.  
  12.     var $fields = array(
  13.         ‘userid’,
  14.         ‘username’,
  15.         ‘password’,
  16.         ‘email’,
  17.         ‘fullname’,
  18.         ‘phone’
  19.     );
  20.  
  21. /**+~~ genStop ~~+**/
  22.  
  23. }
  24.  
  25. ?>

Form Definition

Code: (Plain Text)
  1. <?php
  2.  
  3. $form = array(
  4.     => array(
  5.         ‘Member Profile’ => array(
  6.             ‘fullname’ => array(
  7.                 ‘name’ => ‘fullname’,
  8.                 ‘title’ => ‘Fullname’,
  9.                 ‘desc’ => ‘Your full name.’,
  10.                 ‘type’ => ‘text’
  11.             ),
  12.             ‘phone’ => array(
  13.                 ‘name’ => ‘phone’,
  14.                 ‘title’ => ‘Phone’,
  15.                 ‘desc’ => ‘Your phone numjber.’,
  16.                 ‘type’ => ‘text’
  17.             )
  18.         )
  19.     )
  20. );
  21.  
  22. ?>

Form Input

Code: (Plain Text)
  1. <?php
  2.  
  3. $in = $this->batch(
  4.  
  5.     $this->new_item(‘p’, ‘fullname’, ’string’)
  6.          -> min(5)
  7.          -> no_html()
  8.          -> max(75, ‘This field must not exceed %d characters.’),
  9.  
  10.     $this->new_item(‘p’, ‘phone’, ’string’)
  11.          -> min(5)
  12.          -> no_html()
  13.          -> max(32, ‘This field must not exceed %d characters.’)
  14.  
  15. );
  16.  
  17. ?>

Controller - Index

Code: (Plain Text)
  1. <?php
  2.  
  3. class FW_Action_Users_Index extends FW_Action_Users
  4. {
  5.     public $templates = array(
  6.         ‘index_template’,
  7.         ‘index_template_bit’
  8.     );
  9.    
  10.     public $breadcrumbs = array(
  11.         ‘root_url/’           => ‘Root Title’,
  12.         ‘Index Page’
  13.     );
  14.    
  15.     public $uses    = array(‘User’);
  16.     public $helpers = array(‘Input’, ‘Pagination’);
  17.    
  18.     protected $options = array(‘items_pp’);
  19.    
  20.     protected function clean_request_params()
  21.     {
  22.         $this->page = $this->Input->new_item(‘u’, ‘page’, ‘integer’)
  23.             -> min(1)
  24.             -> get();
  25.  
  26.         /*
  27.         $this->Pagination->set_record_type(’pagination_type’);
  28.         $this->Pagination->set_current_page($this->page);
  29.         $this->Pagination->set_per_page($this->options[’items_pp’]);
  30.        
  31.         $this->User->enable_pagination($this->Pagination);
  32.         */
  33.     }
  34.    
  35.     public function display($input = null)
  36.     {
  37.         extract($this->get_template_vars());
  38.        
  39.         $recordbits = $this->construct_record_bits();
  40.         #$pagination = $this->Pagination->render(true);
  41.        
  42.         return eval(‘return "’ . fetch_template($this->templates[0]) . ‘";’);
  43.     }
  44.    
  45.     protected function construct_record_bits()
  46.     {
  47.         $bits = ;
  48.         $color = ‘alt2′;
  49.        
  50.         foreach ($this->User->fetch_all() as $comment)
  51.         {
  52.             $color = $color == ‘alt1′ ? ‘alt2′ : ‘alt1′;           
  53.             eval(‘$bits .= "’ . fetch_template(‘index_template_bit’) . ‘";’);
  54.         }
  55.        
  56.         return $bits;
  57.     }
  58.    
  59.     public function construct_navbits()
  60.     {
  61.         $urls   = array_keys($this->breadcrumbs);
  62.         $titles = array_values($this->breadcrumbs);
  63.        
  64.         $urls[1]   = sprintf($urls[1], $this->info[‘user_id’]);
  65.         $titles[1] = sprintf($titles[1], $this->info[‘name_field’]);
  66.        
  67.         $urls[2]   = sprintf($urls[2], $this->id);
  68.        
  69.         $this->breadcrumbs = array_combine($urls, $titles);
  70.        
  71.         return parent::construct_navbits();
  72.     }
  73.  
  74. }
  75.  
  76. ?>

Controller - Edit

Code: (Plain Text)
  1. <?php
  2.  
  3. class FW_Action_Users_Edit extends FW_Action_Users
  4. {
  5.     public $templates = array(
  6.         ‘edit_template’
  7.     );
  8.    
  9.     public $breadcrumbs = array(
  10.         ‘root_url/’        => ‘Root Title’,
  11.         ‘root_url/view/%d’ => ‘%s’,
  12.         ‘Edit’
  13.     );
  14.    
  15.     public $uses    = array(‘User’);
  16.     public $helpers = array(‘Input’, ‘HTML’);
  17.    
  18.     protected $options = array();
  19.    
  20.     protected $id;
  21.     protected $info;
  22.    
  23.     protected $form_id = ‘edit_user’;
  24.    
  25.     protected function clean_request_params($params)
  26.     {          
  27.         $this->id = $this->Input->new_value($params[0], ‘id’, ‘integer’)
  28.             -> min(0)
  29.             -> get();
  30.            
  31.         $this->info = $this->User->fetch($this->id) or
  32.             $this->error(‘Invalid page specified.’);
  33.     }
  34.    
  35.     public function display($input = null)
  36.     {
  37.         extract($this->get_template_vars());
  38.        
  39.         $errors = array();
  40.         $data   = $this->info;
  41.                
  42.         if ($input)
  43.         {
  44.             $errors = $input->errors;
  45.             $data   = array_merge($input->data, array(‘user_id’ => $this->id));
  46.         }
  47.        
  48.         $form = $this->HTML->form($this->form_id, $data, $errors);     
  49.         return eval(‘return "’ . fetch_template($this->templates[0]) . ‘";’);
  50.     }
  51.    
  52.     public function submit()
  53.     {      
  54.         $input = $this->Input->handle_form($this->form_id);
  55.        
  56.         if (!empty($input->errors))
  57.         {
  58.             return $this->display($input);
  59.         }
  60.        
  61.         // entire array is saved, so modify if necessary
  62.         $save = $input->data + array(‘user_id’ => $this->id);
  63.        
  64.         $this->User->update(‘user’, $save);
  65.                
  66.         $this->redirect("view_url/$this->id", ‘Saving Record’);
  67.        
  68.     }
  69.    
  70.     public function construct_navbits()
  71.     {      
  72.         $urls   = array_keys($this->breadcrumbs);
  73.         $titles = array_values($this->breadcrumbs);
  74.        
  75.         $urls[1]   = sprintf($urls[1], $this->info[‘user_id’]);
  76.         $titles[1] = sprintf($titles[1], $this->info[‘name_field’]);
  77.  
  78.         $this->breadcrumbs = array_combine($urls, $titles);
  79.        
  80.         return parent::construct_navbits();
  81.     }
  82. }
  83.  
  84. ?>

2 Responses to “Framework Update”  

  1. 1 Psionic Vision

    This is a great idea that has a lot of potential, but I have a few questions:
    1) What is this construct that you have:

    [PHP]
    $object -> method()
    ->method2
    -> method3
    [/PHP]

    I assume you are intending to call different methods of the same object in sequence. But is there such a construct? I mean, if I just say $obj->method()->method2(), then the manual defines this as “calling method2 of the obj returned by $obj-method()”. Unless each of your methods returns an object, this wouldn’t make a lot of sense in your context. Please explain.

    2) Where is the actual code generator, is it available somewhere for download?

  2. 2 Psionic Vision

    Also, maybe we can cooperate on creating this one too? I’m very interested, except I would like this to be taken a step beyond vbulletin, such that it could be used to generate standalone code for use in any context.

Leave a Reply