Location: PHPKode > projects > SolarPHP > solar-system-1.1.1/solar/source/solar/Solar/Cli/MakeModel.php
<?php
/**
 * 
 * Solar command to make a model class from an SQL table.
 * 
 * @category Solar
 * 
 * @package Solar_Cli
 * 
 * @author Paul M. Jones <hide@address.com>
 * 
 * @license http://opensource.org/licenses/bsd-license.php BSD
 * 
 * @version $Id: MakeModel.php 4597 2010-06-15 21:11:48Z pmjones $
 * 
 */
class Solar_Cli_MakeModel extends Solar_Controller_Command
{
    /**
     * 
     * Default configuration values.
     * 
     * @config string extends The default model class to extend.
     * 
     * @var array
     * 
     */
    protected $_Solar_Cli_MakeModel = array(
        'extends' => null,
    );
    
    /**
     * 
     * The base directory where we will write the class file to, typically
     * the local PEAR directory.
     * 
     * @var string
     * 
     */
    protected $_target = null;
    
    /**
     * 
     * The table name we're making the model from.
     * 
     * @var string
     * 
     */
    protected $_table_name = null;
    
    /**
     * 
     * The columns from the table.
     * 
     * @var array
     * 
     */
    protected $_table_cols = array();
    
    /**
     * 
     * The indexes from the table.
     * 
     * @var string
     * 
     */
    protected $_index_info = array();
    
    /**
     * 
     * The class name this model extends from.
     * 
     * @var string
     * 
     */
    protected $_extends = null;
    
    /**
     * 
     * Array of model-class templates (skeletons).
     * 
     * @var array
     * 
     */
    protected $_tpl = array();
    
    /**
     * 
     * Is the model class inherited or not?
     * 
     * @var bool
     * 
     */
    protected $_inherit = false;
    
    /**
     * 
     * Write out a series of model, record, and collection classes for a model.
     * 
     * @param string $class The target class name for the model.
     * 
     * @return void
     * 
     */
    protected function _exec($class = null)
    {
        // we need a class name, at least
        if (! $class) {
            throw $this->_exception('ERR_NO_CLASS');
        }
        
        // are we making multiple classes?
        if (substr($class, -2) == '_*') {
            $prefix = substr($class, 0, -2);
            return $this->_execMulti($prefix);
        }
        
        $this->_outln("Making model '$class'.");
        
        // load the templates
        $this->_loadTemplates();
        
        // we need a target directory
        $this->_setTarget();
        
        // we need a table name
        $this->_setTable($class);
        
        // extend this class
        $this->_setExtends($class);
        
        // using inheritance?
        $this->_setInherit();
        
        // write the model/record/collection files
        $this->_writeModel($class);
        $this->_writeRecord($class);
        $this->_writeCollection($class);
        
        // write the metadata file
        if ($this->_inherit) {
            $this->_outln('Using inheritance, so skipping metadata.');
        } else {
            $this->_loadMetadata();
            $this->_writeMetadata($class);
        }
        
        // write out locale info
        $this->_createLocaleDir($class);
        $this->_writeLocaleFile($class);
        
        // done!
        $this->_outln('Done.');
    }
    
    /**
     * 
     * Makes one model/record/collection class for each table in the database 
     * using a class-name prefix.
     * 
     * @param string $prefix The prefix for each model class name.
     * 
     * @return void
     * 
     */
    protected function _execMulti($prefix)
    {
        $this->_outln("Making one '$prefix' class for each table in the database.");
        
        // get the list of tables
        $this->_out('Connecting to database for table list ... ');
        $sql = Solar::factory('Solar_Sql', $this->_getSqlConfig());
        $this->_outln('connected.');
        $list = $sql->fetchTableList();
        $this->_outln('Found ' . count($list) . ' tables.');
        
        // process each table in turn
        $inflect = Solar_Registry::get('inflect');
        foreach ($list as $table) {
            $name = $inflect->underToStudly($table);
            $class = "{$prefix}_$name";
            $this->_outln("Using table '$table' to make class '$class'.");
            $this->_exec($class);
        }
    }
    
    /**
     * 
     * Loads the template array from skeleton files.
     * 
     * @return void
     * 
     */
    protected function _loadTemplates()
    {
        $this->_tpl = array();
        $dir = Solar_Class::dir($this, 'Data');
        $list = glob($dir . '*.php');
        foreach ($list as $file) {
            // strip .php off the end of the file name
            $key = substr(basename($file), 0, -4);
            
            // we need to add the php-open tag ourselves, instead of
            // having it in the template file, becuase the PEAR packager
            // complains about parsing the skeleton code.
            $this->_tpl[$key] = "<?php\n" . file_get_contents($file);
        }
    }
    
    /**
     * 
     * Sets the base directory target.
     * 
     * @return void
     * 
     */
    protected function _setTarget()
    {
        // use the solar system 'include' directory.
        // that should automatically point to the right vendor for us.
        $target = Solar::$system . '/include';
        $this->_target = Solar_Dir::fix($target);
        $this->_outln("Will write to '{$this->_target}'.");
    }
    
    /**
     * 
     * Sets the table name; determines from the class name if no table name is
     * given.
     * 
     * @param string $class The class name for the model.
     * 
     * @return void
     * 
     */
    protected function _setTable($class)
    {
        $table = $this->_options['table'];
        
        if (! $table) {
            // try to determine from the class name
            $pos = strpos($class, 'Model_');
            if (! $pos) {
                throw $this->_exception('ERR_CANNOT_DETERMINE_TABLE', array(
                    'class' => $class,
                ));
            }
            
            // convert Solar_Model_TableName to table_name
            $table = substr($class, $pos + 6);
            $table = preg_replace('/([a-z])([A-Z])/', '$1_$2', $table);
            $table = strtolower($table);
        }
        
        $this->_table_name = $table;
        
        $this->_outln("Using table '{$this->_table_name}'.");
    }
    
    /**
     * 
     * Sets the $_inherit property based on the $_extends value.
     * 
     * @return void
     * 
     */
    protected function _setInherit()
    {
        if (substr($this->_extends, -6) == '_Model') {
            $this->_inherit = false;
            $this->_outln('Not using inheritance.');
        } else {
            $this->_inherit = true;
            $this->_outln('Using inheritance.');
        }
    }
    
    /**
     * 
     * Sets the class this model will extend from.
     * 
     * @param string $class The model class name.
     * 
     * @return void
     * 
     */
    protected function _setExtends($class)
    {
        // explicit as cli option?
        $extends = $this->_options['extends'];
        if ($extends) {
            $this->_extends = $extends;
            return;
        }
        
        // explicit as config value?
        $extends =  $this->_config['extends'];
        if ($extends) {
            $this->_extends = $this->_config['extends'];
            return;
        }
        
        // look at the class name and find a Vendor_Sql_Model class
        $vendor = Solar_Class::vendor($class);
        $file = $this->_target . "$vendor/Sql/Model.php";
        if (file_exists($file)) {
            $this->_extends = "{$vendor}_Sql_Model";
            return;
        }
        
        // final fallback: Solar_Sql_Model
        $this->_extends = 'Solar_Sql_Model';
        return;
    }
    
    /**
     * 
     * Gets the SQL connection parameters from the command line options.
     * 
     * @return array An array of SQL connection parameters suitable for 
     * passing as a Solar_Sql_Adapter class config.
     * 
     */
    protected function _getSqlConfig()
    {
        $config = array();
        $list = array('adapter', 'host', 'port', 'user', 'pass', 'name');
        foreach ($list as $key) {
            $val = $this->_options[$key];
            if ($val) {
                $config[$key] = $val;
            }
        }
        return $config;
    }
    
    /**
     * 
     * Writes the model class file.
     * 
     * @param string $class The model class name.
     * 
     * @return void
     * 
     */
    protected function _writeModel($class)
    {
        // the target file
        $file = $this->_target
              . str_replace('_', DIRECTORY_SEPARATOR, $class)
              . '.php';
        
        // does the class file already exist?
        if (file_exists($file)) {
            $this->_outln('Model class exists.');
            return;
        }
        
        // create the class dir before attempting to write the model class
        $dir = Solar_Dir::fix(
            $this->_target . str_replace('_', '/', $class)
        );
        
        if (! file_exists($dir)) {
            $this->_out('Making class directory ... ');
            mkdir($dir, 0755, true);
            $this->_outln('done.');
        }
        
        // get the class model template
        $tpl_key = $this->_inherit ? 'model-inherit' : 'model';
        $text = str_replace(
            array('{:class}', '{:extends}'),
            array($class, $this->_extends),
            $this->_tpl[$tpl_key]
        );
        
        $this->_out('Writing model class ... ');
        file_put_contents($file, $text);
        $this->_outln('done.');
    }
    
    /**
     * 
     * Writes the record class file.
     * 
     * @param string $class The model class name.
     * 
     * @return void
     * 
     */
    protected function _writeRecord($class)
    {
        $file = $this->_target
              . str_replace('_', DIRECTORY_SEPARATOR, $class)
              . DIRECTORY_SEPARATOR
              . 'Record.php';
        
        if (file_exists($file)) {
            $this->_outln('Record class exists.');
            return;
        }
        
        $text = str_replace(
            array('{:class}', '{:extends}'),
            array($class, $this->_extends),
            $this->_tpl['record']
        );
        
        $this->_out('Writing record class ... ');
        file_put_contents($file, $text);
        $this->_outln('done.');
    }
    
    /**
     * 
     * Writes the collection class file.
     * 
     * @param string $class The model class name.
     * 
     * @return void
     * 
     */
    protected function _writeCollection($class)
    {
        $file = $this->_target
              . str_replace('_', DIRECTORY_SEPARATOR, $class)
              . DIRECTORY_SEPARATOR
              . 'Collection.php';
        
        if (file_exists($file)) {
            $this->_outln('Collection class exists.');
            return;
        }
        
        $text = str_replace(
            array('{:class}', '{:extends}'),
            array($class, $this->_extends),
            $this->_tpl['collection']
        );
        
        $this->_out('Writing collection class ... ');
        file_put_contents($file, $text);
        $this->_outln('done.');
    }
    
    /**
     * 
     * Reads and retains the table metadata from the database.
     * 
     * @return void
     * 
     */
    protected function _loadMetadata()
    {
        if (! $this->_options['connect']) {
            $this->_outln('Will not connect to database for metadata.');
            return;
        }
        
        $this->_out('Connecting to database for metadata ... ');
        $sql = Solar::factory('Solar_Sql', $this->_getSqlConfig());
        $this->_outln('connected.');
        
        // fetch table cols
        $this->_out('Fetching table cols ... ');
        $this->_table_cols = $sql->fetchTableCols($this->_table_name);
        if (! $this->_table_cols) {
            throw $this->_exception('ERR_NO_COLS', array(
                'table' => $this->_table_name
            ));
        }
        $this->_outln('done.');
        
        // fetch index info
        $this->_out('Fetching index info ... ');
        $this->_index_info = $sql->fetchIndexInfo($this->_table_name);
        if (! $this->_index_info) {
            $this->_outln('no indexes found.');
            return;
        } else {
            $this->_outln('done.');
        }
    }
    
    /**
     * 
     * Writes the metadata class file.
     * 
     * @param string $class The model class name.
     * 
     * @return void
     * 
     */
    protected function _writeMetadata($class)
    {
        $file = $this->_target
              . str_replace('_', DIRECTORY_SEPARATOR, $class)
              . DIRECTORY_SEPARATOR
              . 'Metadata.php';
        
        $table_name = var_export($this->_table_name, true);
        $preg       = "/\=\> \n(\s+)array/m";
        $replace    = "=> array";
        $table_cols = preg_replace($preg, $replace, var_export($this->_table_cols, true));
        $index_info = preg_replace($preg, $replace, var_export($this->_index_info, true));
        
        $str_replace = array(
            '{:class}'      => $class,
            '{:extends}'    => $this->_extends,
            '{:table_name}' => $table_name,
            '{:table_cols}' => trim(preg_replace('/^/m', '    ', $table_cols)),
            '{:index_info}' => trim(preg_replace('/^/m', '    ', $index_info)),
        );
        
        $text = str_replace(
            array_keys($str_replace),
            array_values($str_replace),
            $this->_tpl['metadata']
        );
        
        $this->_out('Writing metadata class ... ');
        file_put_contents($file, $text);
        $this->_outln('done.');
    }
    
    /**
     * 
     * Creates the model "Locale/" directory.
     * 
     * @param string $class The model class name.
     * 
     * @return void
     * 
     */
    protected function _createLocaleDir($class)
    {
        // get the right dir
        $dir = Solar_Dir::fix(
            $this->_target . str_replace('_', '/', $class) . '/Locale'
        );
        
        if (! file_exists($dir)) {
            $this->_out('Creating locale directory ... ');
            mkdir($dir, 0755, true);
            $this->_outln('done.');
        } else {
            $this->_outln('Locale directory exists.');
        }
    }
    
    /**
     * 
     * Writes the model "Locale/en_US.php" file.
     * 
     * @param string $class The model class name.
     * 
     * @return void
     * 
     */
    protected function _writeLocaleFile($class)
    {
        $dir = Solar_Dir::fix(
            $this->_target . str_replace('_', '/', $class) . '/Locale'
        );
        
        // does the locale file already exist?
        $file = $dir . DIRECTORY_SEPARATOR . 'en_US.php';
        if (file_exists($file)) {
            $this->_outln('Locale file for en_US already exists.');
            return;
        }
        
        // does it exist?
        if (! $this->_table_cols) {
            $this->_outln('Not creating locale file; no table_cols available.');
            return;
        }
        
        // create a label value & descr placeholder for each column
        $list = array_keys($this->_table_cols);
        $label = array();
        $descr = array();
        foreach ($list as $col) {
            $key = strtoupper("LABEL_{$col}");
            $label[$key] = ucwords(str_replace('_', ' ', $col));
            
            $key = strtoupper("DESCR_{$col}");
            $descr[$key] = '';
        }
        
        // write the en_US file
        $this->_out('Saving locale file for en_US ... ');
        $data = array_merge($label, $descr);
        $text = var_export($data, true);
        file_put_contents($file, "<?php return $text;");
        $this->_outln('done.');
    }
}
Return current item: SolarPHP