/** * Loads the schema into memory according to the config (e.g. only includes * databases and tables the config allows). * * This function is mostly the first pass in schema generator. A good second * pass would be to attempt to link any relationships not possible through * FK detection. See {@link linkRelationships()}. * * @return void **/ public function loadSchema() { $dsn = $this->config->getDsn(); // Load the driver-specific classes $driver = ucfirst(strtolower($dsn['driver'])); $this->loadDrivers(dirname(__FILE__) . '/drivers/mysql/', $driver); // Construct the server/schema class and start loading databases according to the configuration options. $serverClass = $driver . 'Server'; $server = new $serverClass($dsn); $dbNames = $server->getAvailableDatabaseNames(); foreach ($dbNames as $dbName) { if ($this->config->shouldProcessDatabase($dbName)) { if ($this->verbose) { echo 'Scanning database `' . $dbName . "`\n"; } $database = $server->loadDatabase($dbName); foreach ($database->getAvailableTableNames() as $tableName) { if ($this->config->shouldProcessTable($dbName, $tableName)) { if ($this->verbose) { echo "\tScanning table `" . $tableName . "`\n"; } $table = $database->loadTable($tableName); $pkOverride = $this->config->getPrimaryKeyOverride($dbName, $tableName); if (!empty($pkOverride)) { // Config could be an array or single string, so convert it as needed: if (!is_array($pkOverride)) { $pkOverride = array($pkOverride); } // Remove any existing PK foreach ($table->getPrimaryKey() as $column) { $column->setIsPrimaryKey(false); } // Now set the PK override foreach ($pkOverride as $columnName) { $column = $table->getColumn($columnName); $column->setIsPrimaryKey(true); } } } else { if ($this->verbose) { echo "\tSkipping table `" . $tableName . "`\n"; } } } } else { if ($this->verbose) { echo 'Skipping database `' . $dbName . "`\n"; } } } $this->setSchema($server); }
/** * Writes all the classes to disk, skipping starter classes if they already exist. * * @return boolean - true if no errors, false if not (use {@link getErrorMessages()}) * @author Anthony Bush **/ public function writeClasses($classes) { $this->errorMessages = array(); // Write all classes to disk, but only write non-starter classes or the starter classes that haven't been written already. foreach ($classes as $class) { $fileName = $this->config->getClassFileName($class); if (!$class->isStarterClass() || !file_exists($fileName)) { $this->writeToFile($fileName, $class->getContents()); } } // Return error status if (!empty($this->errorMessages)) { return false; } else { return true; } }
/** * Scans the specified database for the specified table name (using the * match_table_name_prefixes config setting to scan for more table names * in the event the given one is not found.) * * This is support method for {@link findTable()}. * * @return mixed - SchemaTable if found, null if not. * @author Anthony Bush **/ protected function findTableInDatabase($tableNameMatch, SchemaDatabase $database) { $tableNamePrefixes = $this->config->getTableNamePrefixes($database); $tableNames = array($tableNameMatch); if (!is_null($tableNamePrefixes)) { foreach ($tableNamePrefixes as $prefix) { $tableNames[] = $prefix . $tableNameMatch; } } $table = null; foreach ($tableNames as $tableName) { $table = $database->getTable($tableName); if (!is_null($table)) { break; } } return $table; }
/** * Generates the starter collection class * * @return string the generated PHP code * @author Anthony Bush **/ protected function generateStarterCollection($table) { $dbName = $table->getDatabase()->getDatabaseName(); $tableName = $table->getTableName(); $starterObjectClassName = $this->config->getStarterObjectClassName($table); $starterCollectionClassName = $this->config->getStarterCollectionClassName($table); $baseObjectClassName = $this->config->getBaseObjectClassName($table); $baseCollectionClassName = $this->config->getBaseCollectionClassName($table); $phpdocTags = $this->generatePhpdocTags($table); $phpdocTags[] = '@see ' . $baseCollectionClassName . ', CoughCollection'; ob_start(); echo "<?php\n\n"; ?> /** * This is the starter class for <?php echo $baseCollectionClassName; ?> . * * <?php echo implode("\n * ", $phpdocTags) . "\n"; ?> **/ class <?php echo $starterCollectionClassName; ?> extends <?php echo $baseCollectionClassName; ?> { } <?php echo "\n?>"; // Add the class $class = new CoughClass(); $class->setContents(ob_get_clean()); $class->setIsStarterClass(true); $class->setIsCollectionClass(true); $class->setClassName($starterCollectionClassName); $class->setDatabaseName($dbName); $class->setTableName($tableName); $this->addGeneratedClass($class); }
public function testConfigs() { $configPaths = glob(dirname(__FILE__) . '/test_configs/test_config*/'); foreach ($configPaths as $configPath) { // 1. Setup DB $this->executeSqlFile($configPath . 'db_setup/db_setup.sql'); // 2. Generate // Which config to use? $schemaGeneratorConfigFile = $configPath . 'generator_config/database_schema_generator.inc.php'; $coughGeneratorConfigFile = $configPath . 'generator_config/cough_generator.inc.php'; // Get the database config $schemaGeneratorConfig = DatabaseSchemaGeneratorConfig::constructFromFile($schemaGeneratorConfigFile); // Load the schema into memory $schemaGenerator = new DatabaseSchemaGenerator($schemaGeneratorConfig); $schema = $schemaGenerator->generateSchema(); // Manipulate the schema (to add any missed relationships, e.g.) $manipulator = new SchemaManipulator($schemaGeneratorConfig); $manipulator->manipulateSchema($schema); // Get the cough generator config $outputDir = $configPath . 'output/'; include $coughGeneratorConfigFile; $coughGeneratorConfig = new CoughGeneratorConfig($config); // Generate files into memory $coughGenerator = new CoughGenerator($coughGeneratorConfig); $classes = $coughGenerator->generateCoughClasses($schema); // Write files to disk $coughWriter = new CoughWriter($coughGeneratorConfig); $this->assertTrue($coughWriter->writeClasses($classes), 'Unable to write classes to disk.'); // 3. Perform comparison $diffCommand = 'diff -r ' . escapeshellarg($configPath . 'expected_output') . ' ' . escapeshellarg($configPath . 'output'); $diffOutput = shell_exec($diffCommand); $message = "Generated output does not match; diff files using:\n" . $diffCommand . "\n\n" . "DIFF OUTPUT:\n" . "==============================================\n" . $diffOutput . "\n" . "==============================================\n\n"; if (!empty($diffOutput)) { $message .= "<: " . substr_count($diffOutput, "\n<") . "\n"; $message .= ">: " . substr_count($diffOutput, "\n>") . "\n\n"; } $this->assertTrue(empty($diffOutput), $message); // 4. Clean up files if (empty($diffOutput)) { $this->removeGeneratedClasses($configPath . 'output/'); } // 5. Clean up DB $this->executeSqlFile($configPath . 'db_setup/db_teardown.sql'); $this->dropAllTables(); } }
protected function showStatusFromConfigPath($configPath) { try { if (!file_exists($configPath)) { echo 'Config path does not exist: ' . $configPath . "\n"; return; } if (!is_dir($configPath)) { echo 'Config path is not a directory: ' . $configPath . "\n"; return; } // Which config to use? $schemaGeneratorConfigFile = $configPath . 'database_schema_generator.inc.php'; $coughGeneratorConfigFile = $configPath . 'cough_generator.inc.php'; // Get the database config $schemaGeneratorConfig = DatabaseSchemaGeneratorConfig::constructFromFile($schemaGeneratorConfigFile); // Load the schema into memory $schemaGenerator = new DatabaseSchemaGenerator($schemaGeneratorConfig); if ($this->verbose) { $schemaGenerator->enableVerbose(); } $schema = $schemaGenerator->generateSchema(); // Dump some verbose messages. // echo "\n"; // $schema->outputRelationshipCounts(); // echo "\n" . 'About to run the SchemaManipulator and re-output the relationships.' . "\n"; // Manipulate the schema (to add any missed relationships, e.g.) $manipulator = new SchemaManipulator($schemaGeneratorConfig); if ($this->verbose) { $manipulator->enableVerbose(); } $manipulator->manipulateSchema($schema); // Dump some verbose messages again to see if the manipulator added anything. // echo "\n"; // $schema->outputRelationshipCounts(); // Get the cough generator config $coughGeneratorConfig = CoughGeneratorConfig::constructFromFile($coughGeneratorConfigFile); // Generate files into memory $coughGenerator = new CoughGenerator($coughGeneratorConfig); $classes = $coughGenerator->generateCoughClasses($schema); // Add some spacing if verbose mode is on if ($this->verbose) { echo "\n"; } // Show info... // Keep track of all the output file paths (we'll scan them for files that aren't being used) $filePaths = array(); // Keep a map of full file name -> CoughClass object $generatedClasses = array(); // Keep track of added, removed, and modified file information $addedFiles = array(); $starterModifiedFiles = array(); $generatedModifiedFiles = array(); $removedFiles = array(); $numFilesWithNoChange = 0; foreach ($classes as $class) { $filePaths[$coughGeneratorConfig->getClassFilePath($class)] = true; $fileName = $coughGeneratorConfig->getClassFileName($class); $generatedClasses[$fileName] = $class; // Go ahead and check if the file was added or modified if (!file_exists($fileName)) { $addedFiles[] = $fileName; } else { $currentFileContents = file_get_contents($fileName); if ($currentFileContents == $class->getContents()) { $numFilesWithNoChange++; } else { if ($class->isStarterClass()) { $starterModifiedFiles[] = $fileName; } else { $generatedModifiedFiles[] = $fileName; } } } } // Check for removed files: foreach ($filePaths as $dir => $shouldScan) { if (file_exists($dir) && is_readable($dir)) { $files = scandir($dir); foreach ($files as $file) { if (preg_match('|^[^.].*\\.class\\.php$|', $file)) { if (!isset($generatedClasses[$dir . $file])) { $removedFiles[] = $dir . $file; } } } } } // Output Removed Files echo "\n"; echo count($removedFiles) . ' Removed Files' . "\n"; foreach ($removedFiles as $file) { echo $file . "\n"; } // Output Added Files echo "\n"; echo count($addedFiles) . ' Added Files' . "\n"; foreach ($addedFiles as $file) { echo $file . "\n"; } // Output Modified Files echo "\n"; echo count($starterModifiedFiles) . ' Modified Files (which will not be modified since they are starter classes)' . "\n"; foreach ($starterModifiedFiles as $file) { echo $file . "\n"; } echo "\n"; echo count($generatedModifiedFiles) . ' Modified Files (which will be overrwitten)' . "\n"; foreach ($generatedModifiedFiles as $file) { echo $file . "\n"; } echo "\n"; echo $numFilesWithNoChange . ' files with no change.' . "\n"; // Output stats $lineCount = 0; foreach ($classes as $class) { $lineCount += substr_count($class->getContents(), "\n"); } echo "\n"; echo 'Statistics of what will be generated:' . "\n"; echo '-------------------------------------' . "\n"; echo 'Number of classes: ' . count($classes) . "\n"; echo 'Number of lines: ' . number_format($lineCount) . "\n"; echo 'One-to-one relationships: ' . $schema->getNumberOfHasOneRelationships() . "\n"; echo 'One-to-many relationships: ' . $schema->getNumberOfHasManyRelationships() . "\n"; if ($this->verbose) { echo "\n" . 'PHP memory usage:' . "\n"; echo number_format(memory_get_usage()) . " used\n"; if (version_compare(phpversion(), '5.2.0', '>=')) { echo number_format(memory_get_usage(true)) . " allocated\n"; } } } catch (Exception $e) { echo $e->getMessage() . "\n"; } }