Programming and Technology
RSS icon Home icon
  • Dynamically Manage Models with Zend_CodeGenerator

    Posted on January 19th, 2010 brandon 2 comments

    The holidays have passed and at last I’ve gotten enough of my obligations out of the way to begin to innovate on some new code as well as play with some of the new offerings in the latest versions of the Zend Framework. I’ve been most interested in particular with Zend_CodeGenerator since I have spent a lot of time in the last year writing boilerplate code for various projects. I find that the bulk of my time is spent prototyping models so I figured it would not be hard to build a model scaffolding script not unlike the ‘rake’ command in ruby on rails and similar commandline tools offered by the many application platforms out there. Rather than killing my wrists pounding out models, their various attributes and associated mutators/accessors, I decided that my time would be more efficiently spent describing my models using a short hand format (preferably xml) and then having a script translate that into actual classes and files. The Doctrine ORM allows you do this very thing translating YAML into Doctrine Models. This was very interesting to me but I chose to take it a step further and have it build all of my Domain Model infrastructure while it was at it (This includes the models, the gateway classes, and the DAO classes).

    Zend_CodeGenerator is a very handy module in that it uses reflection to to modify existing classes. This opens a world of possibilities for us since we can generate our models and then carry on customizing them with hand written code, and then come back at a later date and modify the model from our xml. Our models can have their data structures managed by our nifty scaffolding script while leaving all of our after-market customizations untouched when we update our classes. It’s like having your cake and eating it too (YUM!).

    So here is an example of how I extended Zend_CodeGenerator classes to build classes specifically for my application platform. This particular example builds gateway classes that extend my Doctrine ORM Adapter (Read about that here) so this is unlikely to be a turnkey drop in solution but it’s really easy to write your own scaffolding classes to suit your needs.

    Let’s start with the model schema. Here is an example schema i’ve created that describes my models:

    /config/schema.xml:

    1.  
    2. <?xml version="1.0"?>
    3. <schema>
    4.     <global>
    5.         <app>Blog</app>
    6.     </global>
    7.     <models>
    8.         <Blog>
    9.           <atts>
    10.             <integer primary="1" autoincrement="1" unsigned="1">id</integer>
    11.             <string required="1">name</string>     
    12.             <datetime>created_at</datetime>
    13.             <datetime>updated_at</datetime>
    14.           </atts>
    15.           <rels>
    16.             <hasmany alias="Posts">Post</hasmany>
    17.             <lookup alias="Administrators">User</lookup>
    18.           </rels>
    19.         </Blog>
    20.         <Post behaviors="Timestampable">
    21.           <atts>
    22.             <integer primary="1" autoincrement="1" unsigned="1">id</integer>
    23.             <string>title</string>
    24.             <string>url_friendly</string>
    25.             <string>status</string>
    26.             <text>content</text>
    27.             <datetime>created_at</datetime>
    28.             <datetime>updated_at</datetime>
    29.             <datetime>published_at</datetime>
    30.           </atts>
    31.           <rels>
    32.             <hasone alias="Blog">Blog</hasone>
    33.             <hasone alias="Author">User</hasone>
    34.             <hasmany alias="Comments">Comment</hasmany>
    35.           </rels>
    36.         </Post>
    37.         <Comment behaviors="Timestampable">
    38.           <atts>
    39.             <integer primary="1" autoincrement="1" unsigned="1">id</integer>
    40.             <string>name</string>
    41.             <string>email</string>  
    42.             <text>comment</text>
    43.           </atts>
    44.           <rels>
    45.              <hasone alias="Post">Post</hasone>
    46.           </rels>
    47.         </Comment>
    48.         <User>
    49.           <atts>
    50.             <integer primary="1" autoincrement="1" unsigned="1">id</integer>
    51.             <string required="1">username</string>
    52.             <string>password</string>
    53.           </atts>
    54.           <rels>
    55.             <hasmany alias="Posts">Post</hasmany>
    56.             <lookup alias="AdministeredBlogs">Blog</lookup>
    57.           </rels>
    58.         </User>
    59.     </models>
    60. </schema>
    61.  

    This xml file describes 4 models (Blog, Post, Comment, and User). It should be clear that we are creating models for a very simple Blogging application. In each model we describe the fields we want and their data types as well as any additional field attributes. We also describe the relationships between the models that map to table relationships. With this schema, I intend to dynamically generate all the files relating to the models.

    You can reference the Zend_CodeGenerator section of the reference manual here, so I won’t bore you with the details of the base classes. The examples provided show how the classes can be used directly to provide reflection as well as construct new classes and save them into files. I use it as more of a framework as it suggests you can do, and I chose to extend the classes provided by the module with custom builder classes, each responsible for generating it’s own aspect of the domain. There is a builder class for the stubbed models, one for the gateway class, one for the DAO class as well as builders for application specific abstraction classes. Let’s start with the scaffolding script that utilizes these classes.

    /scripts/mm.php:

    1.  
    2. #!/usr/bin/php
    3. <?php
    4.  
    5. define(‘BASE_DIR’, ‘../’);
    6.  
    7. ini_set(‘display_errors’, ‘on’);
    8. ini_set(‘memory_limit’, ‘128M’);
    9.  
    10. require_once(BASE_DIR . ‘lib/bootstrap.php’);
    11.  
    12. class ModelManager {
    13.  
    14.     protected $_model_definitions = null;
    15.     protected $_app_name = null;
    16.  
    17.     protected $_schema = null;
    18.  
    19.     protected $_base_path = null;
    20.     protected $_verbose = false;
    21.  
    22.     protected $_lookup_tables = array();
    23.  
    24.     public function __construct(array $pOptions=array()){
    25.         $this->_base_path = BASE_DIR . ‘app/default/’;
    26.  
    27.         if (array_key_exists(‘verbose’, $pOptions))
    28.             $this->_verbose = $pOptions[‘verbose’];
    29.     }
    30.  
    31.     public function setAppName($pAppName){
    32.         $this->_app_name = $pAppName;
    33.     }
    34.  
    35.     public function loadSchema($pFile){
    36.         $this->_model_definitions = realpath($pFile);
    37.  
    38.         if (file_exists($this->_model_definitions)){
    39.             $this->_schema = simplexml_load_file($this->_model_definitions);
    40.         } else
    41.             throw new Exception(‘Schema file does not exist where specified’);
    42.     }
    43.  
    44.     protected function buildModel($pModelNode){ 
    45.         $attributes = array();
    46.         $relationships = array();
    47.         $behaviors = array();
    48.  
    49.         if (isset($pModelNode->attributes()->behaviors))
    50.             $behaviors = explode(‘,’, $pModelNode->attributes()->behaviors);
    51.        
    52.         if (isset($pModelNode->atts)){
    53.            
    54.             foreach ($pModelNode->atts->children() as $att){
    55.                            
    56.                 $attribute = array(
    57.                     ‘name’ => (string) $att,
    58.                     ‘type’ => $att->getName()
    59.                 );
    60.  
    61.                 if (isset($att->attributes()->length))
    62.                     $attribute[‘length’] = (string) $att->attributes()->length;
    63.  
    64.                 if (isset($att->attributes()->unsigned))
    65.                     $attribute[‘unsigned’] = (string) $att->attributes()->unsigned;
    66.  
    67.                 if (isset($att->attributes()->primary))
    68.                     $attribute[‘primary’] = (string) $att->attributes()->primary;
    69.  
    70.                 if (isset($att->attributes()->autoincrement))
    71.                     $attribute[‘autoincrement’] = (string) $att->attributes()->autoincrement;
    72.  
    73.                 if (isset($att->attributes()->default))
    74.                     $attribute[‘default’] = (string) $att->attributes()->default;
    75.  
    76.                 if (isset($att->attributes()->required))
    77.                     $attribute[‘required’] = (string) $att->attributes()->required;
    78.            
    79.                 $attributes[] = $attribute;
    80.             }
    81.         }
    82.  
    83.         if (isset($pModelNode->rels)){
    84.             $rels = $pModelNode->rels->children();
    85.             foreach ($rels as $rel)
    86.                 $relatts = $rel->attributes();
    87.  
    88.                 if ($rel->getName() != ‘lookup’){
    89.                     $relationships[] = array(
    90.                         ‘type’ => $rel->getName(),
    91.                         ‘foreign’ => (string) $rel,
    92.                         ‘alias’ => ((isset($relatts->alias)) ? ((string) $relatts->alias) : $rel->getName())
    93.                     );
    94.                 } else if ($rel->getName() == ‘lookup’){
    95.                     $dao_model_name = $pModelNode->getName() . ((string) $rel);
    96.                    
    97.                     $reverse_dao_model_name = ((string) $rel) . $pModelNode->getName();
    98.  
    99.                     $relationships[] = array(
    100.                         ‘type’ => ‘hasmany’,
    101.                         ‘foreign’ => (string) $rel,
    102.                         ‘alias’ => ((isset($relatts->alias)) ? ((string) $relatts->alias) : $rel->getName()),
    103.                         ‘lookup_class’ => $dao_model_name
    104.                     );
    105.  
    106.                     if (!in_array($dao_model_name, $this->_lookup_tables) && !in_array($reverse_dao_model_name, $this->_lookup_tables)){
    107.  
    108.                         $lrelationships = array(
    109.                             array(
    110.                                 ‘type’ => ‘hasone’,
    111.                                 ‘foreign’ => (string) $rel,
    112.                                 ‘alias’ => (string) $rel,
    113.                                 ‘lookup’ => 1
    114.                             ),
    115.                             array(
    116.                                 ‘type’ => ‘hasone’,
    117.                                 ‘foreign’ => $pModelNode->getName(),
    118.                                 ‘alias’ => $pModelNode->getName(),
    119.                                 ‘lookup’ => 1
    120.                             ),
    121.                         );
    122.  
    123.                         $lubuilder = new FP_Model_Gateway_Doctrine_Builder($this->_app_name, $dao_model_name, array(), $lrelationships);
    124.                         $lubuilder->generateFile();
    125.  
    126.                         $this->_lookup_tables[] = $dao_model_name;
    127.                     }
    128.                 }
    129.         }
    130.        
    131.         $mbuilder = new FP_Model_Builder($this->_app_name, $pModelNode->getName(), $attributes, $relationships);
    132.         $mbuilder->generateFile();
    133.        
    134.         $daobuilder = new FP_Model_Gateway_Doctrine_Builder($this->_app_name, $pModelNode->getName(), $attributes, $relationships, $behaviors);
    135.         $daobuilder->generateFile();
    136.  
    137.         $gwbuilder = new FP_Model_Gateway_Builder($this->_app_name, $pModelNode->getName(), $relationships);
    138.         $gwbuilder->generateFile();
    139.     }
    140.  
    141.     public function process(){
    142.         $this->setAppName($this->_schema->global->app);
    143.         echo ‘Setting application name -> ‘ . $this->_app_name . "\n";
    144.  
    145.         $gwbase = new FP_Model_Gateway_Abstract_Builder($this->_app_name, ‘FP_Model_Gateway_Doctrine_Adapter’);
    146.         $gwbase->generateFile();
    147.  
    148.         $mbase = new FP_Model_Abstract_Builder($this->_app_name);
    149.         $mbase->generateFile();
    150.  
    151.         $cbase = new FP_Model_Collection_Builder($this->_app_name);
    152.         $cbase->generateFile();
    153.  
    154.         foreach ($this->_schema->models->children() as $mnode){
    155.             $this->buildModel($mnode);
    156.         }
    157.     }
    158.  
    159. }
    160.  
    161. try {
    162.  
    163.     $opts = new Zend_Console_Getopt(
    164.         array(
    165.             ’schema|s=s’ => ‘Schema file’,
    166.             ‘verbose|v’ => ‘Be Verbose’
    167.         )
    168.     );
    169.  
    170.     $opts->parse();
    171.  
    172.     if (!isset($opts->s)){
    173.         echo ‘Must specify schema file!’;
    174.         exit;
    175.     }
    176.    
    177. } catch (Zend_Console_Getopt_Exception $x){
    178.     echo $x->getUsageMessage();
    179.     exit;
    180. }
    181.  
    182.  
    183. try {
    184.     $verbose = false;
    185.     if ($opts->v)
    186.         $verbose = true;
    187.     $mm = new ModelManager(array(‘verbose’ => $verbose));
    188.     $mm->loadSchema($opts->s);
    189.     $mm->process();
    190. } catch (Exception $x){
    191.     echo "Error: " . $x->getMessage() . "\n" . "Trace: " . $x->getTraceAsString();
    192.     exit;
    193. }
    194.  
    195. echo ‘done’;
    196.  
    197. ?>
    198.  

    This script lives in /scripts alongside the rest of my application platform (You can read about the folder hierarchies in some of my earlier posts) and is designed to be run from the command line. I include my bootstrapping file in order to harness that setting of my inclue paths but you can just as easily configure your include path directly in this script as long as you remember to include your Zend folder and configure your autoloader. The script requires you to pass in the -s option and the path to your xml schema file. The script will parse your schema file and use our builder classes to create php files and place them in the correct folders.

    It’s worth noting that the scaffolding script is not aware of how the classes are actually built, it only knows how to parse the schema file and convert it into data that the builder class constructors can consume. For the most part, all this script is doing is extracting the application namespace, the models, their attributes, and their relationships and letting the builder classes do all the work. This script can easily be extended to generate different types of classes with the data it extracts.

    Before processing the models, we first create an abstract Gateway, Model, and Collection class that will be extended by all of our other classes. We do this in order to facilitate easy customization of all subclasses by providing an empty parent class that in turn extends the platform’s base model classes.

    1.  
    2.        $gwbase = new FP_Model_Gateway_Abstract_Builder($this->_app_name, ‘FP_Model_Gateway_Doctrine_Adapter’);
    3.         $gwbase->generateFile();
    4.  
    5.         $mbase = new FP_Model_Abstract_Builder($this->_app_name);
    6.         $mbase->generateFile();
    7.  
    8.         $cbase = new FP_Model_Collection_Builder($this->_app_name);
    9.         $cbase->generateFile();
    10.  

    /lib/FP/Model/Gateway/Abstract/Builder.php:

    1.  
    2. <?php
    3.  
    4. class FP_Model_Gateway_Abstract_Builder extends FP_Class_Builder {
    5.  
    6.     protected $_app_name = null;
    7.     protected $_class_name = null;
    8.     protected $_base_path = null;
    9.     protected $_base_class = null;
    10.  
    11.     protected $_existing_class = null;
    12.  
    13.     public function __construct($pApp, $pAdapterClass, $pBaseDir=null){
    14.         $this->setBaseDir($pBaseDir);
    15.        
    16.         $this->_app_name = $pApp;
    17.         $this->_class_name = "{$pApp}_Gateway";
    18.         $this->_base_class = $pAdapterClass;
    19.  
    20.         if ($this->exists()){
    21.             require_once($this->getFilePath());
    22.             $this->_existing_class = parent::fromReflection(new Zend_Reflection_Class($this->_class_name));
    23.         }
    24.     }
    25.  
    26.     public function exists(){
    27.         return (file_exists($this->getFilePath()));
    28.     }
    29.  
    30.     public function getFilePath(){
    31.         $name = $this->getClassName();
    32.         $class_parts = explode(‘_’, $name);
    33.         $basepath = $this->_base_dir . ‘app/default/lib/’;
    34.         return $basepath .  implode(‘/’, $class_parts) . ‘.php’;
    35.     }
    36.  
    37.     public function generate(){
    38.         $this->setName($this->_class_name);
    39.         $this->setExtendedClass($this->_base_class);
    40.  
    41.         $this->setAbstract(true);
    42.  
    43.         $properties = array(
    44.             array(
    45.                 ‘name’ => ‘_primary_key’,
    46.                 ‘visibility’ => ‘protected’,
    47.                 ‘defaultValue’ => ‘id’
    48.             ),
    49.             array(
    50.                 ‘name’ => ‘_model_collection_class’,
    51.                 ‘visibility’ => ‘protected’,
    52.                 ‘defaultValue’ => "{$this->_app_name}_Model_Collection"
    53.             )
    54.         );
    55.  
    56.         foreach ($properties as $prop)
    57.             $this->setProperty($prop);
    58.  
    59.         if ($this->_existing_class){
    60.             $eprops = $this->_existing_class->getProperties();
    61.             $emethods = $this->_existing_class->getMethods();
    62.  
    63.             foreach ($eprops as $ep){
    64.                 if (!$this->hasProperty($ep->getName()))
    65.                     $this->setProperty($ep);
    66.             }
    67.  
    68.             foreach ($emethods as $em){
    69.                 if (!$this->hasMethod($em->getName()))
    70.                     $this->setMethod($em);
    71.             }
    72.         }
    73.    
    74.         return parent::generate();
    75.     }
    76. }
    77. ?>
    78.  

    You’ll probably notice this class extends another custom class called FP_Class_Builder. This is nothing more than another abstraction class which provides some base reusable functionality that we need in our builders, such as the ability to convert class names from camel case and back and some other misc methods which I won’t bore you with. The FP_Class_Builder extends Zend_CodeGenerator_PHP_Class giving all of the functionality provided from that class including the ability to reflect, get/set properties and methods, etc. What I have done in each class is extend the constructor to build the class names using the conventions my application platform uses. For the abstract gateway, since it is not model specific, we simply build the class name with the name of the application. With an app name of ‘Blog’ as specified in the schema, the name of this class will be ‘Blog_Gateway’. All model gateways will extend this class. The two other classes the scaffolder initially builds are similar except that they build abstraction classes for the models themselves, and the model collection.

    1.  
    2.         foreach ($this->_schema->models->children() as $mnode){
    3.             $this->buildModel($mnode);
    4.         }
    5.  

    Next we loop through each model node in the schema and pass it into our method for parsing the model information.

    1.  
    2.          if (isset($pModelNode->atts)){
    3.            
    4.             foreach ($pModelNode->atts->children() as $att){
    5.                            
    6.                 $attribute = array(
    7.                     ‘name’ => (string) $att,
    8.                     ‘type’ => $att->getName()
    9.                 );
    10.  
    11.                 if (isset($att->attributes()->length))
    12.                     $attribute[‘length’] = (string) $att->attributes()->length;
    13.  
    14.                 if (isset($att->attributes()->unsigned))
    15.                     $attribute[‘unsigned’] = (string) $att->attributes()->unsigned;
    16.  
    17.                 if (isset($att->attributes()->primary))
    18.                     $attribute[‘primary’] = (string) $att->attributes()->primary;
    19.  
    20.                 if (isset($att->attributes()->autoincrement))
    21.                     $attribute[‘autoincrement’] = (string) $att->attributes()->autoincrement;
    22.  
    23.                 if (isset($att->attributes()->default))
    24.                     $attribute[‘default’] = (string) $att->attributes()->default;
    25.  
    26.                 if (isset($att->attributes()->required))
    27.                     $attribute[‘required’] = (string) $att->attributes()->required;
    28.            
    29.                 $attributes[] = $attribute;
    30.             }
    31.         }
    32.  
    1.  
    2.           <atts>
    3.             <integer primary="1" autoincrement="1" unsigned="1">id</integer>
    4.             <string required="1">name</string>     
    5.             <datetime>created_at</datetime>
    6.             <datetime>updated_at</datetime>
    7.           </atts>
    8.  

    First the attributes are parse for each model. They are stored in an associative array along with any recognizable attributes it can get from each node. The attributes tags themselves are the datatype to be used and the text inside is the name. As you can see in the example, each attribute can have the typical configuration associated with database columns such as the ability to force the field to be required. This can be extended to include validation configuration as well pretty easily.

    1.  
    2.         if (isset($pModelNode->rels)){
    3.             $rels = $pModelNode->rels->children();
    4.             foreach ($rels as $rel)
    5.                 $relatts = $rel->attributes();
    6.  
    7.                 if ($rel->getName() != ‘lookup’){
    8.                     $relationships[] = array(
    9.                         ‘type’ => $rel->getName(),
    10.                         ‘foreign’ => (string) $rel,
    11.                         ‘alias’ => ((isset($relatts->alias)) ? ((string) $relatts->alias) : $rel->getName())
    12.                     );
    13.                 } else if ($rel->getName() == ‘lookup’){
    14.                     $dao_model_name = $pModelNode->getName() . ((string) $rel);
    15.                    
    16.                     $reverse_dao_model_name = ((string) $rel) . $pModelNode->getName();
    17.  
    18.                     $relationships[] = array(
    19.                         ‘type’ => ‘hasmany’,
    20.                         ‘foreign’ => (string) $rel,
    21.                         ‘alias’ => ((isset($relatts->alias)) ? ((string) $relatts->alias) : $rel->getName()),
    22.                         ‘lookup_class’ => $dao_model_name
    23.                     );
    24.  
    25.                     if (!in_array($dao_model_name, $this->_lookup_tables) && !in_array($reverse_dao_model_name, $this->_lookup_tables)){
    26.  
    27.                         $lrelationships = array(
    28.                             array(
    29.                                 ‘type’ => ‘hasone’,
    30.                                 ‘foreign’ => (string) $rel,
    31.                                 ‘alias’ => (string) $rel,
    32.                                 ‘lookup’ => 1
    33.                             ),
    34.                             array(
    35.                                 ‘type’ => ‘hasone’,
    36.                                 ‘foreign’ => $pModelNode->getName(),
    37.                                 ‘alias’ => $pModelNode->getName(),
    38.                                 ‘lookup’ => 1
    39.                             ),
    40.                         );
    41.  
    42.                         $lubuilder = new FP_Model_Gateway_Doctrine_Builder($this->_app_name, $dao_model_name, array(), $lrelationships);
    43.                         $lubuilder->generateFile();
    44.  
    45.                         $this->_lookup_tables[] = $dao_model_name;
    46.                     }
    47.                 }
    48.         }
    49.  
    1.  
    2.           <rels>
    3.             <hasmany alias="Posts">Post</hasmany>
    4.             <lookup alias="Administrators">User</lookup>
    5.           </rels>
    6.  

    Most of the complexity in these class builders involve defining relationships between models. From the schema you can see there are three relationship types defined: hasone, hasmany, and lookup (many to many). These map perfectly to relational SQL relationships. The tag itself is clearly the type of relationship, and the value inside of the tag is the model it has the relationship with. The model name is used, since the rest can be inferred by convention. The alias attribute is the name by which the related model is reference. In the example above, the relationship to the User model would use the alias ‘Administrators’ and the resulting getter would be called ‘getAdministrators’.

    Any time a many-to-many (ie lookup) relationship is defined, a lookup table must be built, and as a result, a DAO class must be built for that lookup table. That is why there is a one-off class built using the FP_Model_Gateway_Doctrine_Builder.

    1.  
    2.         $mbuilder = new FP_Model_Builder($this->_app_name, $pModelNode->getName(), $attributes, $relationships);
    3.         $mbuilder->generateFile();
    4.        
    5.         $daobuilder = new FP_Model_Gateway_Doctrine_Builder($this->_app_name, $pModelNode->getName(), $attributes, $relationships, $behaviors);
    6.         $daobuilder->generateFile();
    7.  
    8.         $gwbuilder = new FP_Model_Gateway_Builder($this->_app_name, $pModelNode->getName(), $relationships);
    9.         $gwbuilder->generateFile();
    10.  

    For each model defined, we create 3 classes: The model class, the DAO class, and the gateway class. It should be noted that if you are not using a domain model programming approach, you can probably simplify this to just one class (ie the model). Each class is constructed with a distinct builder class. To each we pass our attributes and relationships and in return we get a class which we can save to a file. If no file path is passed to the call to ‘generateFile’, it will save the class to a location that follows the convention of my own application platform.

    /lib/FP/Model/Builder.php:

    1.  
    2. <?php
    3.  
    4. class FP_Model_Builder extends FP_Class_Builder {
    5.  
    6.     protected $_model_name = null;    
    7.     protected $_app_name = null;
    8.     protected $_class_name = null;
    9.     protected $_attributes = null;
    10.     protected $_relationships = null;
    11.     protected $_base_class = null;
    12.  
    13.     protected $_existing_class = null;
    14.  
    15.     public function __construct($pApp, $pModelName, array $pAttributes=array(), array $pRelationships=array(), $pBaseDir=null){
    16.         $this->setBaseDir($pBaseDir);
    17.        
    18.         $this->_app_name = $pApp;
    19.         $this->_model_name = $pModelName;
    20.         $this->_attributes = $pAttributes;
    21.         $this->_relationships = $pRelationships;
    22.         $this->_class_name = "{$pApp}{$pModelName}";
    23.         $this->_base_class = "{$pApp}_Model";
    24.  
    25.         if ($this->exists()){
    26.             require_once($this->getFilePath());
    27.             $this->_existing_class = parent::fromReflection(new Zend_Reflection_Class($this->_class_name));
    28.         }
    29.     }
    30.  
    31.     public function getFilePath(){
    32.         $name = $this->getClassName();
    33.         $class_parts = explode(‘_’, $name);
    34.         $basepath = $this->_base_dir . ‘app/default/models/’;
    35.         return $basepath .  implode(‘/’, $class_parts) . ‘.php’;
    36.     }
    37.  
    38.     public function getModelName(){
    39.         return $this->_model_name;
    40.     }
    41.  
    42.     public function getAppName(){
    43.         return $this->_app_name;
    44.     }
    45.  
    46.     public function getAttributes(){
    47.         return $this->_attributes;
    48.     }
    49.  
    50.     public function getRelationships(){
    51.         return $this->_relationships;
    52.     }
    53.  
    54.     public function exists(){
    55.         $path = $this->getFilePath();
    56.  
    57.         return file_exists($this->getFilePath());
    58.     }
    59.  
    60.     public function generate(){
    61.         $this->setName($this->_class_name);
    62.         $this->setExtendedClass($this->_base_class);
    63.  
    64.         $attributes = array();
    65.         foreach ($this->_attributes as $att){
    66.             $attributes[$att[‘name’]] = null;
    67.         }
    68.  
    69.         $properties = array();
    70.    
    71.         $methods = array();
    72.  
    73.         foreach ($this->_relationships as $rel){
    74.  
    75.             if ($rel[‘type’] == ‘hasone’){
    76.                 $att_name = $this->from_camel_case($rel[‘alias’]) . ‘_id’;
    77.                 $attributes[$att_name] = null;
    78.                 $lazy_member_name = ‘_’ . $this->from_camel_case($rel[‘alias’]);
    79.                
    80.                 $lazy_member = new Zend_CodeGenerator_Php_Property();
    81.                 $lazy_member->setName($lazy_member_name);
    82.                 $lazy_member->setVisibility(‘protected’);              
    83.                 $properties[] = $lazy_member;
    84.  
    85.             } elseif ($rel[‘type’] == ‘hasmany’) {
    86.                 $lazy_member_name = ‘_’ . $this->from_camel_case($rel[‘alias’]);
    87.                 $lazy_member = new Zend_CodeGenerator_Php_Property();
    88.                 $lazy_member->setName($lazy_member_name);
    89.                 $lazy_member->setVisibility(‘protected’);              
    90.                 $properties[] = $lazy_member;
    91.  
    92.             }
    93.  
    94.             $var_name = ‘$this->’ . $lazy_member_name;
    95.             $method_name = ‘get’ . $rel[‘alias’];
    96.  
    97.             $methods[] = array(
    98.                 ‘name’ => $method_name,
    99.                 ‘parameters’ => array(
    100.                     array(
    101.                         ‘name’ => ‘pRefresh’,
    102.                         ‘defaultValue’ => false
    103.                     )
    104.                 ),
    105.                 ‘body’ => ‘if ( ‘ . $var_name . ‘ === null || $pRefresh )’ . "\n\t" . $var_name . ‘ = $this->gateway->’ . $method_name . ‘($this);’ . "\n\n" . ‘return ‘ . $var_name . ‘;’
    106.             );
    107.         }
    108.  
    109.         $properties[] = array(
    110.             ‘name’ => ‘_data’,
    111.             ‘visibility’ => ‘protected’,
    112.             ‘defaultValue’ => $attributes
    113.         );
    114.        
    115.         foreach ($properties as $prop)
    116.             $this->setProperty($prop);
    117.  
    118.         foreach ($methods as $method)
    119.             $this->setMethod($method);
    120.  
    121.         if ($this->_existing_class){
    122.             $eprops = $this->_existing_class->getProperties();
    123.             $emethods = $this->_existing_class->getMethods();
    124.  
    125.             foreach ($eprops as $ep){
    126.                 if (!$this->hasProperty($ep->getName()))
    127.                     $this->setProperty($ep);
    128.             }
    129.  
    130.             foreach ($emethods as $em){
    131.                 if (!$this->hasMethod($em->getName()))
    132.                     $this->setMethod($em);
    133.             }
    134.         }
    135.         return parent::generate();
    136.     }
    137. }
    138. ?>
    139.  

    This is the model builder. It takes the application name, the model name, an array of attributes, and an array of relationships and from there it can construct our stubbed model. Attributes are stored in an associative assigned to a class property called ‘_data’ as per my convention. Relationships are translated into lazy loaded properties and the methods needed to populate those properties from the gateway.

    If the scaffolding script is ran more than once and the generated model already exists, the existing class will first be reflected and remembered. While the new class is being generated, any custom methods and/or properties that are defined in the old code but not in the schema, are merged into the new class and the old class is replaced.

    From our schema, it should generate a Post class that looks as follows:

    /app/default/models/BlogPost.php:

    1.  
    2. <?php
    3. class BlogPost extends Blog_Model {
    4.  
    5.    protected $_data = array(
    6.      ‘id’ => null,
    7.      ‘title’ => null,
    8.      ‘url_friendly’ => null,
    9.      ’status’ => null,
    10.      ‘content’ => null,
    11.      ‘created_at’ => null,
    12.      ‘updated_at’ => null,
    13.      ‘publisehd_at’ => null
    14.    );
    15.  
    16.    protected $_blog = null;
    17.    protected $_author = null;
    18.    protected $_comments = null;
    19.  
    20.    public function getBlog($pRefresh=false){
    21.      if ($this->_blog === null || $pRefresh)
    22.         $this->_blog = $this->gateway->getBlog($this);
    23.  
    24.      return $this->_blog;
    25.    }
    26.  
    27.    public function getAuthor($pRefresh=false){
    28.      if ($this->_author === null || $pRefresh)
    29.         $this->_author = $this->gateway->getAuthor($this);
    30.  
    31.      return $this->_author;
    32.    }
    33.  
    34.    public function getComments($pRefresh=false){
    35.      if ($this->_comments === null || $pRefresh)
    36.         $this->_comments = $this->gateway->getComments($this);
    37.  
    38.      return $this->_comments;
    39.    }
    40.  
    41. }
    42. ?>
    43.  

    The gateway builder is similar in function:

    /lib/FP/Model/Gateway/Builder.php:

    1.  
    2. <?php
    3.  
    4. class FP_Model_Gateway_Builder extends FP_Class_Builder {
    5.  
    6.     protected $_model_name = null;
    7.     protected $_app_name = null;
    8.     protected $_class_name = null;    
    9.     protected $_dao_class = null;
    10.     protected $_model_class = null;
    11.     protected $_relationships = null;
    12.     protected $_base_class = null;
    13.  
    14.     protected $_existing_class = null;
    15.  
    16.     public function __construct($pApp, $pModelName, array $pRelationships=array(), $pBaseDir=null){
    17.         $this->setBaseDir($pBaseDir);
    18.         $this->_app_name = $pApp;
    19.         $this->_model_name = $pModelName;
    20.         $this->_relationships = $pRelationships;
    21.  
    22.         $this->_class_name = "{$pApp}_{$pModelName}s"
    23.         $this->_dao_class = "{$pApp}_Dao_{$pModelName}";
    24.         $this->_model_class = "{$pApp}{$pModelName}";
    25.  
    26.         $this->_base_class = "{$pApp}_Gateway";
    27.  
    28.         if ($this->exists()){
    29.             require_once($this->getFilePath());
    30.             $this->_existing_class = parent::fromReflection(new Zend_Reflection_Class($this->_class_name));
    31.         }
    32.     }
    33.  
    34.     public function getFilePath(){
    35.         $name = $this->getClassName();
    36.         $class_parts = explode(‘_’, $name);
    37.         $basepath = $this->_base_dir . ‘app/default/lib/’;
    38.         return $basepath .  implode(‘/’, $class_parts) . ‘.php’;
    39.     }
    40.  
    41.     public function generate(){
    42.         $this->setName($this->_class_name);
    43.         $this->setExtendedClass($this->_base_class);
    44.  
    45.         $properties = array(
    46.             array(
    47.                 ‘name’ => ‘_dao_class’,
    48.                 ‘visibility’ => ‘protected’,
    49.                 ‘defaultValue’ => $this->_dao_class
    50.             ),
    51.             array(
    52.                 ‘name’ => ‘_model_class’,
    53.                 ‘visibility’ => ‘protected’,
    54.                 ‘defaultValue’ => $this->_model_class
    55.             )
    56.         );
    57.  
    58.         $methods = array();
    59.  
    60.         foreach ($this->_relationships as $rel){
    61.             if ($rel[‘type’] == ‘hasone’){
    62.                 $member_name = ‘get’ . $rel[‘alias’];
    63.                 $local_var = $this->from_camel_case($this->_model_name);
    64.                 $local_key = $this->from_camel_case($rel[‘alias’]) . ‘_id’;
    65.                 $foreign_gateway_class = "{$this->_app_name}_{$rel['foreign']}s";
    66.                 $foreign_gateway_var = "{$rel['foreign']}s";
    67.  
    68.                 $methods[] = array(
    69.                     ‘name’ => $member_name,
    70.                     ‘visibility’ => ‘public’,
    71.                     ‘parameters’ => array(
    72.                         array(
    73.                             ‘name’ => "p{$this->_model_name}"
    74.                         )
    75.                     ),
    76.                     ‘body’ => ‘$’ . $local_var . ‘ = $this->getObject($p’ . $this->_model_name . ‘);’ . "\n\n" . ‘$’ . $foreign_gateway_var . ‘ = new ‘ . $foreign_gateway_class . ‘();’ . "\n\n" . ‘return $’ . $foreign_gateway_var . ‘->fetch($’ . $local_var .‘->’ . $local_key . ‘);’
    77.                 );
    78.  
    79.             } elseif ($rel[‘type’] == ‘hasmany’) {
    80.                 $member_name = ‘get’ . $rel[‘alias’];
    81.                 $local_var = $this->from_camel_case($this->_model_name) . ‘_id’;
    82.                 $foreign_plural = "{$rel['foreign']}s";
    83.                 $foreign_gateway_class = "{$this->_app_name}_{$rel['foreign']}s";
    84.                 $foreign_gateway_var = "{$rel['foreign']}s";
    85.  
    86.                 $methods[] = array(
    87.                     ‘name’ => $member_name,
    88.                     ‘visibility’ => ‘public’,
    89.                     ‘parameters’ => array(
    90.                         array(
    91.                             ‘name’ => "p{$this->_model_name}"
    92.                         )
    93.                     ),
    94.                     ‘body’ => ‘$’ . $local_var . ‘ = $this->getObjectId($p’ . $this->_model_name . ‘);’ . "\n\n" . ‘$’ . $foreign_gateway_var . ‘ = new ‘ . $foreign_gateway_class . ‘();’ . "\n\n" . ‘return $’ . $foreign_gateway_var . ‘->get’ . $foreign_plural . ‘For’ . $this->_model_name .‘($’ . $local_var . ‘);’
    95.                 );
    96.  
    97.             }
    98.  
    99.         }
    100.  
    101.         foreach ($properties as $prop)
    102.             $this->setProperty($prop);
    103.  
    104.         foreach ($methods as $method)
    105.             $this->setMethod($method);
    106.  
    107.         if ($this->_existing_class){
    108.             $eprops = $this->_existing_class->getProperties();
    109.             $emethods = $this->_existing_class->getMethods();
    110.  
    111.             foreach ($eprops as $ep){
    112.                 if (!$this->hasProperty($ep->getName()))
    113.                     $this->setProperty($ep);
    114.             }
    115.  
    116.             foreach ($emethods as $em){
    117.                 if (!$this->hasMethod($em->getName()))
    118.                     $this->setMethod($em);
    119.             }
    120.         }
    121.  
    122.         return parent::generate();
    123.     }
    124. }
    125. ?>
    126.  

    The generated gateway class will extend the abstract Blog_Gateway class and as a result will use whatever ORM adapter the parent class is configured to use. From the defined relationships, this class will build out the implementations for fetching related records from foreign gateway classes. The resulting Post gateway class should look like the following:

    /app/default/lib/Blog/Posts.php:

    1.  
    2. <?php
    3. class Blog_Posts extends Blog_Gateway {
    4.  
    5.    protected $_dao_class = ‘Blog_Dao_Post’;
    6.    protected $_model_class = ‘BlogPost’;
    7.  
    8.    public function getBlog($pPost){
    9.      $post = $this->getObject($pPost);
    10.      $Blogs = new Blog_Blogs();
    11.      return $Blogs->fetch($post->blog_id);
    12.    }
    13.  
    14.    /* snip */
    15. }
    16. ?>
    17.  

    Finally here is the builder class for the DAO model:

    /lib/FP/Model/Gateway/Doctrine/Builder.php:

    1.  
    2. <?php
    3.  
    4. class FP_Model_Gateway_Doctrine_Builder extends FP_Class_Builder {
    5.  
    6.     protected $_model_name = null;
    7.     protected $_app_name = null;
    8.     protected $_class_name = null;    
    9.     protected $_table_name = null;
    10.     protected $_attributes = null;
    11.     protected $_relationships = null;
    12.     protected $_behaviors = null;
    13.     protected $_properties = array();
    14.     protected $_base_class = null;
    15.  
    16.     protected $_existing_class = null;
    17.  
    18.     public function __construct($pApp, $pModelName, array $pAttributes=array(), array $pRelationships=array(), array $pBehaviors=array(), $pBaseDir=null){
    19.         $this->setBaseDir($pBaseDir);
    20.        
    21.         $this->_app_name = $pApp;
    22.         $this->_model_name = $pModelName;
    23.         $this->_attributes = $pAttributes;
    24.         $this->_relationships = $pRelationships;
    25.         $this->_behaviors = $pBehaviors;
    26.         $this->_class_name = "{$pApp}_Dao_{$pModelName}";
    27.         $this->_base_class = "Doctrine_Record";
    28.         $this->_table_name = strtolower($pApp) . ‘_’ . strtolower($this->from_camel_case($pModelName));
    29.  
    30.         if ($this->exists()){
    31.             require_once($this->getFilePath());
    32.             $this->_existing_class = parent::fromReflection(new Zend_Reflection_Class($this->_class_name));
    33.         }
    34.     }
    35.  
    36.     public function getFilePath(){
    37.         $name = $this->getClassName();
    38.         $class_parts = explode(‘_’, $name);
    39.         $basepath = $this->_base_dir . ‘app/default/lib/’;
    40.         return $basepath .  implode(‘/’, $class_parts) . ‘.php’;
    41.     }
    42.  
    43.     protected function buildColumn($att){
    44.         switch ($att[‘type’]){
    45.             case ’string’:
    46.                 $length = (array_key_exists(‘length’, $att)) ? $att[‘length’] : 255;
    47.                 break;
    48.             case ‘integer’:
    49.                 $length = (array_key_exists(‘length’, $att)) ? $att[‘length’] : 4;
    50.                 break;
    51.             default:
    52.                 $length = null;
    53.         }
    54.  
    55.         $col = ‘$this->hasColumn(\’ . $att[‘name’] . \’, \’ . $att[‘type’] . \’, ‘ . (($length !== null) ? strval($length) : ‘null’) . ‘, array(‘;
    56.         $col .= \’type\’ => \’ . $att[‘type’] . \’;
    57.  
    58.         if ($length !== null)
    59.             $col .= ‘ ,\’length\’ => ‘ . $length;
    60.  
    61.         if (array_key_exists(‘unsigned’, $att))
    62.             $col .= ‘, \’unsigned\’ => ‘ . (($att[‘unsigned’]) ? 1 : 0);
    63.  
    64.         if (array_key_exists(‘primary’, $att))
    65.             $col .= ‘, \’primary\’ => ‘ . (($att[‘primary’]) ? ‘true’ : ‘false’);
    66.  
    67.         if (array_key_exists(‘autoincrement’, $att))
    68.             $col .= ‘, \’autoincrement\’ => ‘ . (($att[‘autoincrement’]) ? ‘true’ : ‘false’);
    69.  
    70.         if (array_key_exists(‘default’, $att)){
    71.             if ($att[‘type’] == ’string’)
    72.                 $col .= ‘, \’default\’ => \’ . $att[‘default’] . \’;
    73.             else if ($att[‘type’] == ‘boolean’)
    74.                 $col .= ‘, \’default\’ => ‘ . (($att[‘default’]) ? ‘true’ : ‘false’);
    75.             else
    76.                 $col .= ‘, \’default\’ => ‘ . $att[‘default’];
    77.         }
    78.  
    79.         if (array_key_exists(‘required’, $att))
    80.             $col .= ‘, \’notnull\’ => ‘ . (($att[‘required’]) ? ‘true’ : ‘false’);
    81.  
    82.         $col .= ‘ ));’;
    83.  
    84.         return $col;
    85.     }
    86.  
    87.     public function generate(){
    88.         $this->setName($this->_class_name);
    89.         $this->setExtendedClass($this->_base_class);
    90.  
    91.         $methods = array(
    92.             ’setTableDefinition’ => array(
    93.                 ‘name’ => ’setTableDefinition’,
    94.                 ‘body’ => ‘$this->setTableName(\’ . $this->_table_name . \’);’
    95.             ),
    96.  
    97.             ’setUp’ => array(
    98.                 ‘name’ => ’setUp’,
    99.                 ‘body’ =>
    100.             )
    101.  
    102.         );     
    103.  
    104.         foreach ($this->_attributes as $att){   
    105.             $col = $this->buildColumn($att);
    106.        
    107.             $methods[’setTableDefinition’][‘body’] .= "\n" . $col;
    108.         }
    109.  
    110.         foreach ($this->_behaviors as $behavior){
    111.             $methods[’setUp’][‘body’] .= "\n" . ‘$this->actAs(\’ . $behavior . \’);’ . "\n";     
    112.         }
    113.  
    114.         foreach ($this->_relationships as $rel){
    115.             $foreign_class = "{$this->_app_name}_Dao_{$rel['foreign']}";
    116.        
    117.             if ($rel[‘type’] == ‘hasone’){
    118.                 $att_name = $this->from_camel_case($rel[‘alias’]) . ‘_id’;
    119.  
    120.                 if (array_key_exists(‘lookup’, $rel) && $rel[‘lookup’]){
    121.                     $field = array(
    122.                         ‘name’ => $att_name,
    123.                         ‘type’ => ‘integer’,
    124.                         ‘required’ => 1
    125.                     );
    126.  
    127.                     $col = $this->buildColumn($field);
    128.                     $methods[’setTableDefinition’][‘body’] .= "\n" . $col;
    129.  
    130.                 }
    131.                
    132.                 $relstr = ‘$this->hasOne(\’ . $foreign_class . ‘ as ‘ . $rel[‘alias’] . \’, array( \’local\’ => \’ . $att_name . \’, \’foreign\’ => \’id\’  ) );’;
    133.                
    134.             } elseif ($rel[‘type’] == ‘hasmany’){
    135.                 $att_name = $this->from_camel_case($this->_model_name) . ‘_id’;
    136.                
    137.                 if (array_key_exists(‘lookup_class’, $rel)){
    138.                     $ref_class = $this->_app_name . ‘_Dao_’ . $rel[‘lookup_class’];
    139.                     $lookup = \’refClass\’ => \’ . $ref_class . \’, ‘;
    140.                 } else
    141.                     $lookup = ;
    142.                
    143.                 $relstr = ‘$this->hasMany(\’ . $foreign_class . ‘ as ‘ . $rel[‘alias’] . \’, array( ‘ . $lookup . \’local\’ => \’id\’, \’foreign\’ => \’ . $att_name . \’  ) );’;
    144.                
    145.             }
    146.            
    147.             $methods[’setUp’][‘body’] .= "\n" . $relstr;
    148.         }
    149.  
    150.         foreach ($methods as $method)
    151.             $this->setMethod($method);
    152.  
    153.         if ($this->_existing_class){
    154.             $eprops = $this->_existing_class->getProperties();
    155.             $emethods = $this->_existing_class->getMethods();
    156.  
    157.             foreach ($eprops as $ep){
    158.                 if (!$this->hasProperty($ep->getName()))
    159.                     $this->setProperty($ep);
    160.             }
    161.  
    162.             foreach ($emethods as $em){
    163.                 if (!$this->hasMethod($em->getName()))
    164.                     $this->setMethod($em);
    165.             }
    166.         }
    167.  
    168.         return parent::generate();
    169.     }
    170. }
    171. ?>
    172.  

    This class builds our ORM model. Our ORM is Doctrine in this case, but this class can be replaced by another version supporting the ORM of your choice. This class will build our DAO class which extends Doctrine_Record and defines the columns on the table that this class abstracts as well as the relationships between this table and other tables.

    The resulting class for our Post DAO class should look as follows:

    /app/default/lib/Blog/Dao/Post.php:

    1.  
    2. <?php
    3. class Blog_Dao_Post extends Doctrine_Record {
    4.  
    5.   public function setTableDefinition(){
    6.     $this->setTableName(‘blog_post’);
    7.     $this->hasColumn(‘id’, ‘integer’, 4, array(‘type’ => ‘integer’ ,‘length’ => 4, ‘unsigned’ => 1, ‘primary’ => true, ‘autoincrement’ => true ));
    8.     $this->hasColumn(‘title’, ’string’, 255, array(‘type’ => ’string’ ,‘length’ => 255 ));
    9.     $this->hasColumn(‘url_friendly’, ’string’, 255, array(‘type’ => ’string’ ,‘length’ => 255 ));
    10.     $this->hasColumn(’status’, ’string’, 255, array(‘type’ => ’string’ ,‘length’ => 255 ));
    11.     $this->hasColumn(‘content’, ‘clob’);
    12.     $this->hasColumn(‘published_at’, ‘datetime’);
    13.     $this->hasColumn(‘blog_id’, ‘integer’, 4);
    14.     $this->hasColumn(‘author_id’, ‘integer’, 4);
    15.   }
    16.  
    17.   public function setUp(){
    18.     $this->actAs(‘Timestampable’);
    19.  
    20.     $this->hasOne(‘Blog_Dao_Blog as Blog’, array(‘local’ => ‘blog_id’, ‘foreign’ => ‘id’);
    21.     $this->hasOne(‘Blog_Dao_User as Author’, array(‘local’ => ‘author_id’, ‘foreign’ => ‘id’);
    22.     $this->hasMany(‘Blog_Dao_Comment as Comments’, array(‘local’ => ‘id’, ‘foreign’ => ‘post_id’));
    23.  
    24.   }
    25. }
    26. ?>
    27.  

    Now that we have all of our pieces in place we can leverage Doctrines migration tools to generate some migrations for us. My migration generating script looks like this:

    /public/util/generate_migrations.php:

    1.  
    2. <?php
    3.  
    4. define(‘BASE_DIR’, ‘../../’);
    5.  
    6. require_once(BASE_DIR .‘lib/bootstrap.php’);
    7.  
    8. set_include_path(BASE_DIR . ‘db/migrations’ . PATH_SEPARATOR . get_include_path());
    9.  
    10. $modules = FP_Application_Module_Broker::getModules();
    11.  
    12. foreach ($modules as $module){
    13.    Doctrine::generateMigrationsFromModels(BASE_DIR . ‘db/migrations’, BASE_DIR . ‘app/’ . $module->getName() . ‘/lib/’ . $module->getAppDir() . ‘/Dao’);
    14.    echo ‘Generated migrations for ‘ . $module->getTitle() . ‘ module<br/>’;
    15. }
    16.  
    17. echo ‘Finished’;
    18.  
    19. ?>
    20.  

    This script will scan all of my models and generate migrations for them and place them in /db/migrations.

    For reference, here is the FP_Class_Builder base class:

    /lib/FP/Class/Builder.php:

    1.  
    2. <?php
    3.  
    4. class FP_Class_Builder extends Zend_CodeGenerator_Php_Class {
    5.  
    6.     protected $_class_name = null;
    7.     protected $_base_dir = null;
    8.  
    9.     protected function setBaseDir($pBaseDir=null){
    10.         if ($pBaseDir)
    11.             $this->_base_dir = $pBaseDir;
    12.         else {
    13.             if (defined(‘BASE_DIR’))
    14.                 $this->_base_dir = BASE_DIR;
    15.             else
    16.                 $this->_base_dir = getcwd();
    17.         }
    18.     }
    19.        
    20.     protected function from_camel_case($str) {
    21.         $str[0] = strtolower($str[0]);
    22.         $func = create_function(‘$c’, ‘return "_" . strtolower($c[1]);’);
    23.         return preg_replace_callback(‘/([A-Z])/’, $func, $str);
    24.     }
    25.  
    26.     protected function to_camel_case($str, $capitalise_first_char = false) {
    27.         if($capitalise_first_char) {
    28.         $str[0] = strtoupper($str[0]);
    29.         }
    30.         $func = create_function(‘$c’, ‘return strtoupper($c[1]);’);
    31.         return preg_replace_callback(‘/_([a-z])/’, $func, $str);
    32.     }
    33.  
    34.     public function getFilePath(){
    35.         return null;
    36.     }
    37.  
    38.     public function exists(){
    39.         return file_exists($this->getFilePath());
    40.     }
    41.  
    42.     public function generateFile($pPath=null){
    43.         $code = $this->generate();
    44.         if ($pPath)
    45.             $path = $pPath;
    46.         else
    47.             $path = $this->getFilePath();
    48.            
    49.         return file_put_contents($path, "<?php \n" . $code . "\n ?>");
    50.     }
    51.  
    52.     public function getClassName(){
    53.         return $this->_class_name;
    54.     }
    55. }
    56.  
    57. ?>
    58.  

    Here is the base functionality for storing the class in a physical file as well as methods to convert to and from camel case.

    So with a simple command from the command line (./mm.php -s ../config/schema.xml), all of my model infrastructure is instantly generated so I am now free to begin by writing my controllers and views. One idea to extend this further is to write a builder script for the model-specific controllers which could house all of the CRUD actions. I see this methodology evolving as an integral part of my own platform, hopefully you can derive some uses for it in your own.

    • Share/Bookmark
     

    2 responses to “Dynamically Manage Models with Zend_CodeGenerator”

    1. Brandon, Excellent post !!! I’m just curious on how you create the schema.xml file… any tool ? because when u have a couple of tables by hand it’s not a problem but when u have 100+ it turns ugly. Thanks !

    2. I do it by hand so I can see how your hand would ache in that scenario. The schema is standard xml so I can see there being gui powered utility generating this schema automatically.

    Leave a reply

Get Adobe Flash playerPlugin by wpburn.com wordpress themes