Esempio n. 1
0
    /**
     * Generates base classes of the models, and if they don't exist,
     * skeleton code for the models themselves. 
     * Use it only after you have made changes to the database schema.
     * You shouldn't be using it on every request.
     * @method generateModels
     * @param {string} $directory The directory in which to generate the files.
     *  If the files already exist, they are not overwritten,
     *  unless they are inside the "Base" subdirectory.
     *  If the "Base" subdirectory does not exist, it is created.
     * @param {string} [$classname_prefix=null] The prefix to prepend to the Base class names.
     *  If not specified, prefix becomes "connectionName_",  where connectionName is the name of the connection.
     * @return {array} $filenames The array of filenames for files that were saved.
     * @throws {Exception} If the $connection is not registered, or the $directory
     *  does not exist, this function throws an exception.
     */
    function generateModels($directory, $classname_prefix = null)
    {
        $dc = '/**';
        if (!file_exists($directory)) {
            throw new Exception("directory {$directory} does not exist.");
        }
        $connectionName = $this->connectionName();
        $conn = Db::getConnection($connectionName);
        $prefix = empty($conn['prefix']) ? '' : $conn['prefix'];
        $prefix_len = strlen($prefix);
        if (!isset($classname_prefix)) {
            $classname_prefix = isset($connectionName) ? $connectionName . '_' : '';
        }
        $rows = $this->rawQuery('SHOW TABLES')->fetchAll();
        if (class_exists('Q_Config')) {
            $ext = Q_Config::get('Q', 'extensions', 'class', 'php');
        } else {
            $ext = 'php';
        }
        $table_classnames = array();
        $js_table_classes_string = '';
        $class_name_prefix = rtrim(ucfirst($classname_prefix), "._");
        $filenames = array();
        foreach ($rows as $row) {
            $table_name = $row[0];
            $table_name_base = substr($table_name, $prefix_len);
            $table_name_prefix = substr($table_name, 0, $prefix_len);
            if (empty($table_name_base) or $table_name_prefix != $prefix or stristr($table_name, '_Q_') !== false) {
                continue;
                // no class generated
            }
            $class_name_base = null;
            $js_base_class_string = '';
            $base_class_string = $this->codeForModelBaseClass($table_name, $directory, $classname_prefix, $class_name_base, null, $js_base_class_string, $table_comment);
            // sets the $class_name variable
            $class_name = ucfirst($classname_prefix) . $class_name_base;
            if (empty($class_name)) {
                continue;
                // no class generated
            }
            $class_name_parts = explode('_', $class_name);
            $class_filename = $directory . DS . implode(DS, $class_name_parts) . '.php';
            $base_class_filename = $directory . DS . 'Base' . DS . implode(DS, $class_name_parts) . '.php';
            $js_class_filename = $directory . DS . implode(DS, $class_name_parts) . '.js';
            $js_base_class_filename = $directory . DS . 'Base' . DS . implode(DS, $class_name_parts) . '.js';
            $js_base_class_require = 'Base' . DS . implode(DS, $class_name_parts);
            $js_class_name = implode('.', $class_name_parts);
            $js_base_class_name = implode('.Base.', $class_name_parts);
            $class_extras = is_readable($class_filename . '.inc') ? file_get_contents($class_filename . '.inc') : '';
            $js_class_extras = is_readable($js_class_filename . '.inc') ? file_get_contents($js_class_filename . '.inc') : '';
            $class_string = <<<EOT
<?php
{$dc}
 * @module {$connectionName}
 */
{$dc}
 * Class representing '{$class_name_base}' rows in the '{$connectionName}' database
 * You can create an object of this class either to
 * access its non-static methods, or to actually
 * represent a {$table_name_base} row in the {$connectionName} database.
 *
 * @class {$class_name}
 * @extends Base_{$class_name}
 */
class {$class_name} extends Base_{$class_name}
{
\t{$dc}
\t * The setUp() method is called the first time
\t * an object of this class is constructed.
\t * @method setUp
\t */
\tfunction setUp()
\t{
\t\tparent::setUp();
\t\t// INSERT YOUR CODE HERE
\t\t// e.g. \$this->hasMany(...) and stuff like that.
\t}

\t/* 
\t * Add any {$class_name} methods here, whether public or not
\t * If file '{$class_name_base}.php.inc' exists, its content is included
\t * * * */
{$class_extras}
\t/* * * */
\t{$dc}
\t * Implements the __set_state method, so it can work with
\t * with var_export and be re-imported successfully.
\t * @method __set_state
\t * @static
\t * @param {array} \$array
\t * @return {{$class_name}} Class instance
\t */
\tstatic function __set_state(array \$array) {
\t\t\$result = new {$class_name}();
\t\tforeach(\$array as \$k => \$v)
\t\t\t\$result->\$k = \$v;
\t\treturn \$result;
\t}
};
EOT;
            $js_class_string = <<<EOT
{$dc}
 * Class representing {$table_name_base} rows.
 *
 * This description should be revised and expanded.
 *
 * @module {$connectionName}
 */
var Q = require('Q');
var Db = Q.require('Db');
var {$class_name_base} = Q.require('{$js_base_class_require}');

{$dc}
 * Class representing '{$class_name_base}' rows in the '{$connectionName}' database
{$table_comment} * @namespace {$class_name_prefix}
 * @class {$class_name_base}
 * @extends Base.{$js_class_name}
 * @constructor
 * @param {Object} fields The fields values to initialize table row as
 * an associative array of `{column: value}` pairs
 */
function {$class_name} (fields) {

\t// Run mixed-in constructors
\t{$class_name}.constructors.apply(this, arguments);

\t/*
\t * Add any privileged methods to the model class here.
\t * Public methods should probably be added further below.
\t * If file '{$class_name_base}.js.inc' exists, its content is included
\t * * * */
{$js_class_extras}
\t/* * * */
}

Q.mixin({$class_name}, {$class_name_base});

/*
 * Add any public methods here by assigning them to {$class_name}.prototype
 */

{$dc}
 * The setUp() method is called the first time
 * an object of this class is constructed.
 * @method setUp
 */
{$class_name}.prototype.setUp = function () {
\t// put any code here
\t// overrides the Base class
};

module.exports = {$class_name};
EOT;
            // overwrite base class file if necessary, but not the class file
            Db_Utils::saveTextFile($base_class_filename, $base_class_string);
            $filenames[] = $base_class_filename;
            Db_Utils::saveTextFile($js_base_class_filename, $js_base_class_string);
            $filenames[] = $js_base_class_filename;
            if (!file_exists($class_filename)) {
                Db_Utils::saveTextFile($class_filename, $class_string);
                $filenames[] = $class_filename;
            }
            if (!file_exists($js_class_filename)) {
                Db_Utils::saveTextFile($js_class_filename, $js_class_string);
                $filenames[] = $js_class_filename;
            }
            $table_classnames[] = $class_name;
            $js_table_classes_string .= <<<EOT

{$dc}
 * Link to {$connectionName}.{$class_name_base} model
 * @property {$class_name_base}
 * @type {$connectionName}.{$class_name_base}
 */
Base.{$class_name_base} = Q.require('{$connectionName}/{$class_name_base}');

EOT;
        }
        // Generate the "module model" base class file
        $table_classnames_exported = var_export($table_classnames, true);
        $table_classnames_json = $pk_json_indented = str_replace(array("[", ",", "]"), array("[\n\t", ",\n\t", "\n]"), json_encode($table_classnames));
        if (!empty($connectionName)) {
            $class_name = Db::generateTableClassName($connectionName);
            $class_name_parts = explode('_', $class_name);
            $class_filename = $directory . DS . implode(DS, $class_name_parts) . '.php';
            $base_class_filename = $directory . DS . 'Base' . DS . implode(DS, $class_name_parts) . '.php';
            $js_class_filename = $directory . DS . implode(DS, $class_name_parts) . '.js';
            $js_base_class_filename = $directory . DS . 'Base' . DS . implode(DS, $class_name_parts) . '.js';
            $js_base_class_require = 'Base' . DS . implode(DS, $class_name_parts);
            // because table name can be {$prefix}_Q_plugin or {$prefix}_Q_app we need to know correct table name
            $tables = $this->rawQuery("SHOW TABLES LIKE '{$prefix}Q_%'")->execute()->fetchAll(PDO::FETCH_NUM);
            if ($tables) {
                $tablename = $tables[0][0];
                $model_comment = $this->rawQuery("SELECT * FROM {$tablename}")->execute()->fetchAll(PDO::FETCH_NUM);
                $model_comment = isset($model_comment[0]) && !empty($model_comment[0][2]) ? " * <br/>{$model_comment[0][2]}\n" : '';
            } else {
                $model_comment = '';
            }
            $model_extras = is_readable($class_filename . '.inc') ? file_get_contents($class_filename . '.inc') : '';
            $js_model_extras = is_readable($js_class_filename . '.inc') ? file_get_contents($js_class_filename . '.inc') : '';
            $base_class_string = <<<EOT
<?php

{$dc}
 * Autogenerated base class for the {$connectionName} model.
 * 
 * Don't change this file, since it can be overwritten.
 * Instead, change the {$class_name}.php file.
 *
 * @module {$connectionName}
 */
{$dc}
 * Base class for the {$class_name} model
 * @class Base_{$class_name}
 */
abstract class Base_{$class_name}
{
\t{$dc}
\t * The list of model classes
\t * @property \$table_classnames
\t * @type array
\t */
\tstatic \$table_classnames = {$table_classnames_exported};

\t{$dc}
     * This method calls Db.connect() using information stored in the configuration.
     * If this has already been called, then the same db object is returned.
\t * @method db
\t * @return {iDb} The database object
\t */
\tstatic function db()
\t{
\t\treturn Db::connect('{$connectionName}');
\t}

\t{$dc}
\t * The connection name for the class
\t * @method connectionName
\t * @return {string} The name of the connection
\t */
\tstatic function connectionName()
\t{
\t\treturn '{$connectionName}';
\t}
};
EOT;
            $js_base_class_string = <<<EOT
{$dc}
 * Autogenerated base class for the {$connectionName} model.
 * 
 * Don't change this file, since it can be overwritten.
 * Instead, change the {$class_name}.js file.
 *
 * @module {$connectionName}
 */
var Q = require('Q');
var Db = Q.require('Db');

{$dc}
 * Base class for the {$class_name} model
 * @namespace Base
 * @class {$class_name}
 * @static
 */
function Base () {
\treturn this;
}
 
module.exports = Base;

{$dc}
 * The list of model classes
 * @property tableClasses
 * @type array
 */
Base.tableClasses = {$table_classnames_json};

{$dc}
 * This method calls Db.connect() using information stored in the configuration.
 * If this has already been called, then the same db object is returned.
 * @method db
 * @return {Db} The database connection
 */
Base.db = function () {
\treturn Db.connect('{$connectionName}');
};

{$dc}
 * The connection name for the class
 * @method connectionName
 * @return {string} The name of the connection
 */
Base.connectionName = function() {
\treturn '{$connectionName}';
};
{$js_table_classes_string}
EOT;
            $class_string = <<<EOT
<?php
{$dc}
 * {$class_name_prefix} model
{$model_comment} * @module {$connectionName}
 * @main {$connectionName}
 */
{$dc}
 * Static methods for the {$connectionName} models.
 * @class {$class_name}
 * @extends Base_{$class_name}
 */
abstract class {$class_name} extends Base_{$class_name}
{
\t/*
\t * This is where you would place all the static methods for the models,
\t * the ones that don't strongly pertain to a particular row or table.
\t * If file '{$class_name}.php.inc' exists, its content is included
\t * * * */
{$model_extras}
\t/* * * */
};
EOT;
            $js_class_string = <<<EOT
{$dc}
 * {$class_name_prefix} model
{$model_comment} * @module {$connectionName}
 * @main {$connectionName}
 */
var Q = require('Q');

{$dc}
 * Static methods for the {$class_name_prefix} model
 * @class {$class_name_prefix}
 * @extends Base.{$class_name_prefix}
 * @static
 */
function {$connectionName}() { };
module.exports = {$connectionName};

var Base_{$connectionName} = Q.require('{$js_base_class_require}');
Q.mixin({$connectionName}, Base_{$connectionName});

/*
 * This is where you would place all the static methods for the models,
 * the ones that don't strongly pertain to a particular row or table.
 * Just assign them as methods of the {$connectionName} object.
 * If file '{$class_name}.js.inc' exists, its content is included
 * * * */
{$js_model_extras}
/* * * */
EOT;
            // overwrite base class file if necessary, but not the class file
            Db_Utils::saveTextFile($base_class_filename, $base_class_string);
            $filenames[] = $base_class_filename;
            Db_Utils::saveTextFile($js_base_class_filename, $js_base_class_string);
            $filenames[] = $js_base_class_filename;
            if (!file_exists($class_filename)) {
                $filenames[] = $class_filename;
                Db_Utils::saveTextFile($class_filename, $class_string);
            }
            if (!file_exists($js_class_filename)) {
                $filenames[] = $js_class_filename;
                Db_Utils::saveTextFile($js_class_filename, $js_class_string);
            }
        }
        $directoryLen = strlen($directory . DS);
        foreach ($filenames as $i => $filename) {
            $filenames[$i] = substr($filename, $directoryLen);
        }
        return $filenames;
    }
Esempio n. 2
0
    /**
     * Generates base classes of the models, and if they don't exist,
     * skeleton code for the models themselves. 
     * Use it only after you have made changes to the database schema.
     * You shouldn't be using it on every request.
     * @param string $conn_name
     *  The name of a previously registered connection.
     * @param string $directory
     *  The directory in which to generate the files.
     *  If the files already exist, they are not overwritten,
     *  unless they are inside the "Base" subdirectory.
     *  If the "Base" subdirectory does not exist, it is created.
     * @param string $classname_prefix
     *  The prefix to prepend to the Base class names.
     *  If not specified, prefix becomes "Conn_Name_", 
     *  where conn_name is the name of the connection.
     * @throws Exception
     *  If the $connection is not registered, or the $directory
     *  does not exist, this function throws an exception.
     */
    function generateModels($directory, $classname_prefix = null)
    {
        if (!file_exists($directory)) {
            throw new Exception("Directory {$directory} does not exist.");
        }
        $conn_name = $this->connectionName();
        $conn = Db::getConnection($conn_name);
        $prefix = empty($conn['prefix']) ? '' : $conn['prefix'];
        $prefix_len = strlen($prefix);
        if (!isset($classname_prefix)) {
            $classname_prefix = isset($conn_name) ? $conn_name . '_' : '';
        }
        $rows = $this->rawQuery('SHOW TABLES')->execute()->fetchAll();
        if (class_exists('Pie_Config')) {
            $ext = Pie_Config::get('pie', 'extensions', 'class', 'php');
        } else {
            $ext = 'php';
        }
        $table_classes = array();
        foreach ($rows as $row) {
            $table_name = $row[0];
            $table_name_base = substr($table_name, $prefix_len);
            $table_name_prefix = substr($table_name, 0, $prefix_len);
            if (empty($table_name_base) or $table_name_prefix != $prefix) {
                continue;
            }
            // no class generated
            $class_name = null;
            $base_class_string = $this->codeForModelBaseClass($table_name, $directory, $classname_prefix, $class_name);
            // sets the $class_name variable
            if (empty($class_name)) {
                continue;
            }
            // no class generated
            $class_string = <<<EOT
<?php

/**
 * Class representing {$table_name_base} rows.
 * You can create an object of this class either to
 * access its non-static methods, or to actually
 * represent a {$table_name_base} row in the {$conn_name} database.
 *
 * This description should be revised and expanded.
 *
 * @package {$conn_name}
 */
class {$class_name} extends Base_{$class_name}
{
\t/**
\t * The setUp() method is called the first time
\t * an object of this class is constructed.
\t */
\tfunction setUp()
\t{
\t\tparent::setUp();
\t\t// INSERT YOUR CODE HERE
\t\t// e.g. \$this->hasMany(...) and stuff like that.
\t}
\t
\t/**
\t * Implements the __set_state method, so it can work with
\t * with var_export and be re-imported successfully.
\t */
\tstatic function __set_state(array \$array) {
\t\t\$result = new {$class_name}();
\t\tforeach(\$array as \$k => \$v)
\t\t\t\$result->\$k = \$v;
\t\treturn \$result;
\t}
};
EOT;
            $class_name_parts = explode('_', $class_name);
            $class_filename = $directory . DS . implode(DS, $class_name_parts) . '.php';
            $base_class_filename = $directory . DS . 'Base' . DS . implode(DS, $class_name_parts) . '.php';
            // overwrite base class file if necessary, but not the class file
            Db_Utils::saveTextFile($base_class_filename, $base_class_string);
            if (!file_exists($class_filename)) {
                Db_Utils::saveTextFile($class_filename, $class_string);
            }
            $table_classes[] = $class_name;
        }
        // Generate the "module model" base class file
        $table_classes_exported = var_export($table_classes, true);
        if (!empty($conn_name)) {
            $class_name = Db::generateTableClassName($conn_name);
            $class_name_parts = explode('_', $class_name);
            $class_filename = $directory . DS . implode(DS, $class_name_parts) . '.php';
            $base_class_filename = $directory . DS . 'Base' . DS . implode(DS, $class_name_parts) . '.php';
            $base_class_string = <<<EOT
<?php

/**
 * Autogenerated base class for the {$conn_name} model.
 * 
 * Don't change this file, since it can be overwritten.
 * Instead, change the {$class_name}.php file.
 *
 * @package {$conn_name}
 */
abstract class Base_{$class_name}
{
\tstatic \$table_classes = {$table_classes_exported};

\t/** @return Db_Mysql */
\tstatic function db()
\t{
\t\treturn Db::connect('{$conn_name}');
\t}

\tstatic function connectionName()
\t{
\t\treturn '{$conn_name}';
\t}
};
EOT;
            $class_string = <<<EOT
<?php

/**
 * Static methods for the {$conn_name} models.
 * This description should be revised and expanded.
 *
 * @package {$conn_name}
 */
abstract class {$class_name} extends Base_{$class_name}
{
\t/**
\t * This is where you would place all the
\t * static methods for the models, the ones
\t * that don't strongly pertain to a particular row
\t * or table.
\t */
};
EOT;
            // overwrite base class file if necessary, but not the class file
            Db_Utils::saveTextFile($base_class_filename, $base_class_string);
            if (!file_exists($class_filename)) {
                Db_Utils::saveTextFile($class_filename, $class_string);
            }
        }
    }