/** * 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; }
/** * 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); } } }