CC-2166: Packaging Improvements. Moved the Zend app into airtime_mvc. It is now installed to /var/www/airtime. Storage is now set to /srv/airtime/stor. Utils are now installed to /usr/lib/airtime/utils/. Added install/airtime-dircheck.php as a simple test to see if everything is install/uninstalled correctly.

This commit is contained in:
Paul Baranowski 2011-04-14 18:55:04 -04:00
parent 514777e8d2
commit b11cbd8159
4546 changed files with 138 additions and 51 deletions

View file

@ -0,0 +1,593 @@
<?php
/**
* This file is part of the Propel package.
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @license MIT License
*/
//include_once 'phing/tasks/ext/CapsuleTask.php';
require_once 'phing/Task.php';
include_once 'config/GeneratorConfig.php';
include_once 'model/AppData.php';
include_once 'model/Database.php';
include_once 'builder/util/XmlToAppData.php';
/**
* An abstract base Propel task to perform work related to the XML schema file.
*
* The subclasses invoke templates to do the actual writing of the resulting files.
*
* @author Hans Lellelid <hans@xmpl.org> (Propel)
* @author Jason van Zyl <jvanzyl@zenplex.com> (Torque)
* @author Daniel Rall <dlr@finemaltcoding.com> (Torque)
* @package propel.generator.task
*/
abstract class AbstractPropelDataModelTask extends Task
{
/**
* Fileset of XML schemas which represent our data models.
* @var array Fileset[]
*/
protected $schemaFilesets = array();
/**
* Data models that we collect. One from each XML schema file.
*/
protected $dataModels = array();
/**
* Have datamodels been initialized?
* @var boolean
*/
private $dataModelsLoaded = false;
/**
* Map of data model name to database name.
* Should probably stick to the convention
* of them being the same but I know right now
* in a lot of cases they won't be.
*/
protected $dataModelDbMap;
/**
* The target database(s) we are generating SQL
* for. Right now we can only deal with a single
* target, but we will support multiple targets
* soon.
*/
protected $targetDatabase;
/**
* DB encoding to use for XmlToAppData object
*/
protected $dbEncoding = 'iso-8859-1';
/**
* Target PHP package to place the generated files in.
*/
protected $targetPackage;
/**
* @var Mapper
*/
protected $mapperElement;
/**
* Destination directory for results of template scripts.
* @var PhingFile
*/
protected $outputDirectory;
/**
* Whether to package the datamodels or not
* @var PhingFile
*/
protected $packageObjectModel;
/**
* Whether to perform validation (XSD) on the schema.xml file(s).
* @var boolean
*/
protected $validate;
/**
* The XSD schema file to use for validation.
* @var PhingFile
*/
protected $xsdFile;
/**
* XSL file to use to normalize (or otherwise transform) schema before validation.
* @var PhingFile
*/
protected $xslFile;
/**
* Optional database connection url.
* @var string
*/
private $url = null;
/**
* Optional database connection user name.
* @var string
*/
private $userId = null;
/**
* Optional database connection password.
* @var string
*/
private $password = null;
/**
* PDO Connection.
* @var PDO
*/
private $conn = false;
/**
* An initialized GeneratorConfig object containing the converted Phing props.
*
* @var GeneratorConfig
*/
private $generatorConfig;
/**
* Return the data models that have been
* processed.
*
* @return List data models
*/
public function getDataModels()
{
if (!$this->dataModelsLoaded) {
$this->loadDataModels();
}
return $this->dataModels;
}
/**
* Return the data model to database name map.
*
* @return Hashtable data model name to database name map.
*/
public function getDataModelDbMap()
{
if (!$this->dataModelsLoaded) {
$this->loadDataModels();
}
return $this->dataModelDbMap;
}
/**
* Adds a set of xml schema files (nested fileset attribute).
*
* @param set a Set of xml schema files
*/
public function addSchemaFileset(Fileset $set)
{
$this->schemaFilesets[] = $set;
}
/**
* Get the current target database.
*
* @return String target database(s)
*/
public function getTargetDatabase()
{
return $this->targetDatabase;
}
/**
* Set the current target database. (e.g. mysql, oracle, ..)
*
* @param v target database(s)
*/
public function setTargetDatabase($v)
{
$this->targetDatabase = $v;
}
/**
* Get the current target package.
*
* @return string target PHP package.
*/
public function getTargetPackage()
{
return $this->targetPackage;
}
/**
* Set the current target package. This is where generated PHP classes will
* live.
*
* @param string $v target PHP package.
*/
public function setTargetPackage($v)
{
$this->targetPackage = $v;
}
/**
* Set the packageObjectModel switch on/off
*
* @param string $v The build.property packageObjectModel
*/
public function setPackageObjectModel($v)
{
$this->packageObjectModel = ($v === '1' ? true : false);
}
/**
* Set whether to perform validation on the datamodel schema.xml file(s).
* @param boolean $v
*/
public function setValidate($v)
{
$this->validate = $v;
}
/**
* Set the XSD schema to use for validation of any datamodel schema.xml file(s).
* @param $v PhingFile
*/
public function setXsd(PhingFile $v)
{
$this->xsdFile = $v;
}
/**
* Set the normalization XSLT to use to transform datamodel schema.xml file(s) before validation and parsing.
* @param $v PhingFile
*/
public function setXsl(PhingFile $v)
{
$this->xslFile = $v;
}
/**
* [REQUIRED] Set the output directory. It will be
* created if it doesn't exist.
* @param PhingFile $outputDirectory
* @return void
* @throws Exception
*/
public function setOutputDirectory(PhingFile $outputDirectory) {
try {
if (!$outputDirectory->exists()) {
$this->log("Output directory does not exist, creating: " . $outputDirectory->getPath(),Project::MSG_VERBOSE);
if (!$outputDirectory->mkdirs()) {
throw new IOException("Unable to create Ouptut directory: " . $outputDirectory->getAbsolutePath());
}
}
$this->outputDirectory = $outputDirectory->getCanonicalPath();
} catch (IOException $ioe) {
throw new BuildException($ioe);
}
}
/**
* Set the current target database encoding.
*
* @param v target database encoding
*/
public function setDbEncoding($v)
{
$this->dbEncoding = $v;
}
/**
* Set the DB connection url.
*
* @param string $url connection url
*/
public function setUrl($url)
{
$this->url = $url;
}
/**
* Set the user name for the DB connection.
*
* @param string $userId database user
*/
public function setUserid($userId)
{
$this->userId = $userId;
}
/**
* Set the password for the DB connection.
*
* @param string $password database password
*/
public function setPassword($password)
{
$this->password = $password;
}
/**
* Get the output directory.
* @return string
*/
public function getOutputDirectory() {
return $this->outputDirectory;
}
/**
* Nested creator, creates one Mapper for this task.
*
* @return Mapper The created Mapper type object.
* @throws BuildException
*/
public function createMapper() {
if ($this->mapperElement !== null) {
throw new BuildException("Cannot define more than one mapper.", $this->location);
}
$this->mapperElement = new Mapper($this->project);
return $this->mapperElement;
}
/**
* Maps the passed in name to a new filename & returns resolved File object.
* @param string $from
* @return PhingFile Resolved File object.
* @throws BuilException - if no Mapper element se
* - if unable to map new filename.
*/
protected function getMappedFile($from)
{
if (!$this->mapperElement) {
throw new BuildException("This task requires you to use a <mapper/> element to describe how filename changes should be handled.");
}
$mapper = $this->mapperElement->getImplementation();
$mapped = $mapper->main($from);
if (!$mapped) {
throw new BuildException("Cannot create new filename based on: " . $from);
}
// Mappers always return arrays since it's possible for some mappers to map to multiple names.
$outFilename = array_shift($mapped);
$outFile = new PhingFile($this->getOutputDirectory(), $outFilename);
return $outFile;
}
/**
* Gets the PDO connection, if URL specified.
* @return PDO Connection to use (for quoting, Platform class, etc.) or NULL if no connection params were specified.
*/
public function getConnection()
{
if ($this->conn === false) {
$this->conn = null;
if ($this->url) {
$buf = "Using database settings:\n"
. " URL: " . $this->url . "\n"
. ($this->userId ? " user: " . $this->userId . "\n" : "")
. ($this->password ? " password: " . $this->password . "\n" : "");
$this->log($buf, Project::MSG_VERBOSE);
// Set user + password to null if they are empty strings
if (!$this->userId) { $this->userId = null; }
if (!$this->password) { $this->password = null; }
try {
$this->conn = new PDO($this->url, $this->userId, $this->password);
$this->conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch (PDOException $x) {
$this->log("Unable to create a PDO connection: " . $x->getMessage(), Project::MSG_WARN);
}
}
}
return $this->conn;
}
/**
* Gets all matching XML schema files and loads them into data models for class.
* @return void
*/
protected function loadDataModels()
{
$ads = array();
// Get all matched files from schemaFilesets
foreach ($this->schemaFilesets as $fs) {
$ds = $fs->getDirectoryScanner($this->project);
$srcDir = $fs->getDir($this->project);
$dataModelFiles = $ds->getIncludedFiles();
$platform = $this->getGeneratorConfig()->getConfiguredPlatform();
// Make a transaction for each file
foreach ($dataModelFiles as $dmFilename) {
$this->log("Processing: ".$dmFilename);
$xmlFile = new PhingFile($srcDir, $dmFilename);
$dom = new DomDocument('1.0', 'UTF-8');
$dom->load($xmlFile->getAbsolutePath());
// modify schema to include any external schemas (and remove the external-schema nodes)
$this->includeExternalSchemas($dom, $srcDir);
// normalize (or transform) the XML document using XSLT
if ($this->getGeneratorConfig()->getBuildProperty('schemaTransform') && $this->xslFile) {
$this->log("Transforming " . $xmlFile->getPath() . " using stylesheet " . $this->xslFile->getPath(), Project::MSG_VERBOSE);
if (!class_exists('XSLTProcessor')) {
$this->log("Could not perform XLST transformation. Make sure PHP has been compiled/configured to support XSLT.", Project::MSG_ERR);
} else {
// normalize the document using normalizer stylesheet
$xslDom = new DomDocument('1.0', 'UTF-8');
$xslDom->load($this->xslFile->getAbsolutePath());
$xsl = new XsltProcessor();
$xsl->importStyleSheet($xslDom);
$dom = $xsl->transformToDoc($dom);
}
}
// validate the XML document using XSD schema
if ($this->validate && $this->xsdFile) {
$this->log("Validating XML doc (".$xmlFile->getPath().") using schema file " . $this->xsdFile->getPath(), Project::MSG_VERBOSE);
if (!$dom->schemaValidate($this->xsdFile->getAbsolutePath())) {
throw new EngineException("XML schema file (".$xmlFile->getPath().") does not validate. See warnings above for reasons validation failed (make sure error_reporting is set to show E_WARNING if you don't see any).", $this->getLocation());
}
}
$xmlParser = new XmlToAppData($platform, $this->getTargetPackage(), $this->dbEncoding);
$ad = $xmlParser->parseString($dom->saveXML(), $xmlFile->getAbsolutePath());
$ad->setName($dmFilename);
$ads[] = $ad;
}
}
if (empty($ads)) {
throw new BuildException("No schema files were found (matching your schema fileset definition).");
}
foreach ($ads as $ad) {
// map schema filename with database name
$this->dataModelDbMap[$ad->getName()] = $ad->getDatabase(null, false)->getName();
}
if (count($ads)>1 && $this->packageObjectModel) {
$ad = $this->joinDataModels($ads);
$this->dataModels = array($ad);
} else {
$this->dataModels = $ads;
}
foreach ($this->dataModels as &$ad) {
$ad->doFinalInitialization();
}
$this->dataModelsLoaded = true;
}
/**
* Replaces all external-schema nodes with the content of xml schema that node refers to
*
* Recurses to include any external schema referenced from in an included xml (and deeper)
* Note: this function very much assumes at least a reasonable XML schema, maybe it'll proof
* users don't have those and adding some more informative exceptions would be better
*
* @param DomDocument $dom
* @param string $srcDir
* @return void (objects, DomDocument, are references by default in PHP 5, so returning it is useless)
**/
protected function includeExternalSchemas(DomDocument $dom, $srcDir) {
$databaseNode = $dom->getElementsByTagName("database")->item(0);
$externalSchemaNodes = $dom->getElementsByTagName("external-schema");
$fs = FileSystem::getFileSystem();
$nbIncludedSchemas = 0;
while ($externalSchema = $externalSchemaNodes->item(0)) {
$include = $externalSchema->getAttribute("filename");
$this->log("Processing external schema: ".$include);
$externalSchema->parentNode->removeChild($externalSchema);
if ($fs->prefixLength($include) != 0) {
$externalSchemaFile = new PhingFile($include);
} else {
$externalSchemaFile = new PhingFile($srcDir, $include);
}
$externalSchemaDom = new DomDocument('1.0', 'UTF-8');
$externalSchemaDom->load($externalSchemaFile->getAbsolutePath());
// The external schema may have external schemas of its own ; recurse
$this->includeExternalSchemas($externalSchemaDom, $srcDir);
foreach ($externalSchemaDom->getElementsByTagName("table") as $tableNode) { // see xsd, datatase may only have table or external-schema, the latter was just deleted so this should cover everything
$databaseNode->appendChild($dom->importNode($tableNode, true));
}
$nbIncludedSchemas++;
}
return $nbIncludedSchemas;
}
/**
* Joins the datamodels collected from schema.xml files into one big datamodel.
* We need to join the datamodels in this case to allow for foreign keys
* that point to tables in different packages.
*
* @param array[AppData] $ads The datamodels to join
* @return AppData The single datamodel with all other datamodels joined in
*/
protected function joinDataModels($ads)
{
$mainAppData = null;
foreach ($ads as $appData) {
if (null === $mainAppData) {
$mainAppData = $appData;
$appData->setName('JoinedDataModel');
continue;
}
// merge subsequent schemas to the first one
foreach ($appData->getDatabases(false) as $addDb) {
$addDbName = $addDb->getName();
if ($mainAppData->hasDatabase($addDbName)) {
$db = $mainAppData->getDatabase($addDbName, false);
// join tables
foreach ($addDb->getTables() as $addTable) {
if ($db->getTable($addTable->getName())) {
throw new BuildException('Duplicate table found: ' . $addDbName . '.');
}
$db->addTable($addTable);
}
// join database behaviors
foreach ($addDb->getBehaviors() as $addBehavior) {
if (!$db->hasBehavior($addBehavior->getName())) {
$db->addBehavior($addBehavior);
}
}
} else {
$mainAppData->addDatabase($addDb);
}
}
}
return $mainAppData;
}
/**
* Gets the GeneratorConfig object for this task or creates it on-demand.
* @return GeneratorConfig
*/
protected function getGeneratorConfig()
{
if ($this->generatorConfig === null) {
$this->generatorConfig = new GeneratorConfig();
$this->generatorConfig->setBuildProperties($this->getProject()->getProperties());
}
return $this->generatorConfig;
}
/**
* Checks this class against Basic requrements of any propel datamodel task.
*
* @throws BuildException - if schema fileset was not defined
* - if no output directory was specified
*/
protected function validate()
{
if (empty($this->schemaFilesets)) {
throw new BuildException("You must specify a fileset of XML schemas.", $this->getLocation());
}
// Make sure the output directory is set.
if ($this->outputDirectory === null) {
throw new BuildException("The output directory needs to be defined!", $this->getLocation());
}
if ($this->validate) {
if (!$this->xsdFile) {
throw new BuildException("'validate' set to TRUE, but no XSD specified (use 'xsd' attribute).", $this->getLocation());
}
}
}
}

View file

@ -0,0 +1,344 @@
<?php
/**
* This file is part of the Propel package.
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @license MIT License
*/
require_once 'phing/Task.php';
require_once 'task/PropelDataModelTemplateTask.php';
require_once 'builder/om/OMBuilder.php';
require_once 'builder/om/ClassTools.php';
/**
* This Task converts the XML runtime configuration file into a PHP array for faster performance.
*
* @author Hans Lellelid <hans@xmpl.org>
* @package propel.generator.task
*/
class PropelConvertConfTask extends AbstractPropelDataModelTask
{
/**
* @var PhingFile The XML runtime configuration file to be converted.
*/
private $xmlConfFile;
/**
* @var string This is the file where the converted conf array dump will be placed.
*/
private $outputFile;
/**
* @var string This is the file where the classmap manifest converted conf array dump will be placed.
*/
private $outputClassmapFile;
/**
* [REQUIRED] Set the input XML runtime conf file.
* @param PhingFile $v The XML runtime configuration file to be converted.
*/
public function setXmlConfFile(PhingFile $v)
{
$this->xmlConfFile = $v;
}
/**
* [REQUIRED] Set the output filename for the converted runtime conf.
* The directory is specified using AbstractPropelDataModelTask#setOutputDirectory().
* @param string $outputFile
* @see AbstractPropelDataModelTask#setOutputDirectory()
*/
public function setOutputFile($outputFile)
{
// this is a string, not a file
$this->outputFile = $outputFile;
}
/**
* [REQUIRED] Set the output filename for the autoload classmap.
* The directory is specified using AbstractPropelDataModelTask#setOutputDirectory().
* @param string $outputFile
* @see AbstractPropelDataModelTask#setOutputDirectory()
*/
public function setOutputClassmapFile($outputFile)
{
// this is a string, not a file
$this->outputClassmapFile = $outputFile;
}
/**
* The main method does the work of the task.
*/
public function main()
{
// Check to make sure the input and output files were specified and that the input file exists.
if (!$this->xmlConfFile || !$this->xmlConfFile->exists()) {
throw new BuildException("No valid xmlConfFile specified.", $this->getLocation());
}
if (!$this->outputFile) {
throw new BuildException("No outputFile specified.", $this->getLocation());
}
// Create a PHP array from the runtime-conf.xml file
$xmlDom = new DOMDocument();
$xmlDom->load($this->xmlConfFile->getAbsolutePath());
$xml = simplexml_load_string($xmlDom->saveXML());
$phpconf = self::simpleXmlToArray($xml);
/* For some reason the array generated from runtime-conf.xml has separate
* 'log' section and 'propel' sections. To maintain backward compatibility
* we need to put 'log' back into the 'propel' section.
*/
$log = array();
if (isset($phpconf['log'])) {
$phpconf['propel']['log'] = $phpconf['log'];
unset($phpconf['log']);
}
if(isset($phpconf['propel'])) {
$phpconf = $phpconf['propel'];
}
// add generator version
$phpconf['generator_version'] = $this->getGeneratorConfig()->getBuildProperty('version');
if (!$this->outputClassmapFile) {
// We'll create a default one for BC
$this->outputClassmapFile = 'classmap-' . $this->outputFile;
}
// Write resulting PHP data to output file
$outfile = new PhingFile($this->outputDirectory, $this->outputFile);
$output = "<?php\n";
$output .= "// This file generated by Propel " . $phpconf['generator_version'] . " convert-conf target".($this->getGeneratorConfig()->getBuildProperty('addTimestamp') ? " on " . strftime("%c") : '') . "\n";
$output .= "// from XML runtime conf file " . $this->xmlConfFile->getPath() . "\n";
$output .= "\$conf = ";
$output .= var_export($phpconf, true);
$output .= ";\n";
$output .= "\$conf['classmap'] = include(dirname(__FILE__) . DIRECTORY_SEPARATOR . '".$this->outputClassmapFile."');\n";
$output .= "return \$conf;";
$this->log("Creating PHP runtime conf file: " . $outfile->getPath());
if (!file_put_contents($outfile->getAbsolutePath(), $output)) {
throw new BuildException("Error creating output file: " . $outfile->getAbsolutePath(), $this->getLocation());
}
// add classmap
$phpconfClassmap = $this->getClassMap();
$outfile = new PhingFile($this->outputDirectory, $this->outputClassmapFile);
$output = '<' . '?' . "php\n";
$output .= "// This file generated by Propel " . $phpconf['generator_version'] . " convert-conf target".($this->getGeneratorConfig()->getBuildProperty('addTimestamp') ? " on " . strftime("%c") : '') . "\n";
$output .= "return ";
$output .= var_export($phpconfClassmap, true);
$output .= ";";
$this->log("Creating PHP classmap runtime file: " . $outfile->getPath());
if (!file_put_contents($outfile->getAbsolutePath(), $output)) {
throw new BuildException("Error creating output file: " . $outfile->getAbsolutePath(), $this->getLocation());
}
} // main()
/**
* Recursive function that converts an SimpleXML object into an array.
* @author Christophe VG (based on code form php.net manual comment)
* @param object SimpleXML object.
* @return array Array representation of SimpleXML object.
*/
private static function simpleXmlToArray($xml)
{
$ar = array();
foreach ( $xml->children() as $k => $v ) {
// recurse the child
$child = self::simpleXmlToArray( $v );
//print "Recursed down and found: " . var_export($child, true) . "\n";
// if it's not an array, then it was empty, thus a value/string
if ( count($child) == 0 ) {
$child = self::getConvertedXmlValue($v);
}
// add the childs attributes as if they where children
foreach ( $v->attributes() as $ak => $av ) {
// if the child is not an array, transform it into one
if ( !is_array( $child ) ) {
$child = array( "value" => $child );
}
if ($ak == 'id') {
// special exception: if there is a key named 'id'
// then we will name the current key after that id
$k = self::getConvertedXmlValue($av);
} else {
// otherwise, just add the attribute like a child element
$child[$ak] = self::getConvertedXmlValue($av);
}
}
// if the $k is already in our children list, we need to transform
// it into an array, else we add it as a value
if ( !in_array( $k, array_keys($ar) ) ) {
$ar[$k] = $child;
} else {
// (This only applies to nested nodes that do not have an @id attribute)
// if the $ar[$k] element is not already an array, then we need to make it one.
// this is a bit of a hack, but here we check to also make sure that if it is an
// array, that it has numeric keys. this distinguishes it from simply having other
// nested element data.
if ( !is_array($ar[$k]) || !isset($ar[$k][0]) ) { $ar[$k] = array($ar[$k]); }
$ar[$k][] = $child;
}
}
return $ar;
}
/**
* Process XML value, handling boolean, if appropriate.
* @param object The simplexml value object.
* @return mixed
*/
private static function getConvertedXmlValue($value)
{
$value = (string) $value; // convert from simplexml to string
// handle booleans specially
$lwr = strtolower($value);
if ($lwr === "false") {
$value = false;
} elseif ($lwr === "true") {
$value = true;
}
return $value;
}
/**
* Lists data model classes and builds an associative array className => classPath
* To be used for autoloading
* @return array
*/
protected function getClassMap()
{
$phpconfClassmap = array();
$generatorConfig = $this->getGeneratorConfig();
foreach ($this->getDataModels() as $dataModel) {
foreach ($dataModel->getDatabases() as $database) {
$classMap = array();
foreach ($database->getTables() as $table) {
if (!$table->isForReferenceOnly()) {
// -----------------------------------------------------
// Add TableMap class,
// Peer, Object & Query stub classes,
// and Peer, Object & Query base classes
// -----------------------------------------------------
// (this code is based on PropelOMTask)
foreach (array('tablemap', 'peerstub', 'objectstub', 'querystub', 'peer', 'object', 'query') as $target) {
$builder = $generatorConfig->getConfiguredBuilder($table, $target);
$this->log("Adding class mapping: " . $builder->getClassname() . ' => ' . $builder->getClassFilePath());
$classMap[$builder->getFullyQualifiedClassname()] = $builder->getClassFilePath();
}
// -----------------------------------------------------
// Add children classes for object and query,
// as well as base child query,
// for single tabel inheritance tables.
// -----------------------------------------------------
if ($col = $table->getChildrenColumn()) {
if ($col->isEnumeratedClasses()) {
foreach ($col->getChildren() as $child) {
foreach (array('objectmultiextend', 'queryinheritance', 'queryinheritancestub') as $target) {
$builder = $generatorConfig->getConfiguredBuilder($table, $target);
$builder->setChild($child);
$this->log("Adding class mapping: " . $builder->getClassname() . ' => ' . $builder->getClassFilePath());
$classMap[$builder->getFullyQualifiedClassname()] = $builder->getClassFilePath();
}
}
}
}
// -----------------------------------------------------
// Add base classes for alias tables (undocumented)
// -----------------------------------------------------
$baseClass = $table->getBaseClass();
if ( $baseClass !== null ) {
$className = ClassTools::classname($baseClass);
if (!isset($classMap[$className])) {
$classPath = ClassTools::getFilePath($baseClass);
$this->log('Adding class mapping: ' . $className . ' => ' . $classPath);
$classMap[$className] = $classPath;
}
}
$basePeer = $table->getBasePeer();
if ( $basePeer !== null ) {
$className = ClassTools::classname($basePeer);
if (!isset($classMap[$className])) {
$classPath = ClassTools::getFilePath($basePeer);
$this->log('Adding class mapping: ' . $className . ' => ' . $classPath);
$classMap[$className] = $classPath;
}
}
// ----------------------------------------------
// Add classes for interface
// ----------------------------------------------
if ($table->getInterface()) {
$builder = $generatorConfig->getConfiguredBuilder($table, 'interface');
$this->log("Adding class mapping: " . $builder->getClassname() . ' => ' . $builder->getClassFilePath());
$classMap[$builder->getFullyQualifiedClassname()] = $builder->getClassFilePath();
}
// ----------------------------------------------
// Add classes from old treeMode implementations
// ----------------------------------------------
if ($table->treeMode() == 'MaterializedPath') {
foreach (array('nodepeerstub', 'nodestub', 'nodepeer', 'node') as $target) {
$builder = $generatorConfig->getConfiguredBuilder($table, $target);
$this->log("Adding class mapping: " . $builder->getClassname() . ' => ' . $builder->getClassFilePath());
$classMap[$builder->getFullyQualifiedClassname()] = $builder->getClassFilePath();
}
}
if ($table->treeMode() == 'NestedSet') {
foreach (array('nestedset', 'nestedsetpeer') as $target) {
$builder = $generatorConfig->getConfiguredBuilder($table, $target);
$this->log("Adding class mapping: " . $builder->getClassname() . ' => ' . $builder->getClassFilePath());
$classMap[$builder->getFullyQualifiedClassname()] = $builder->getClassFilePath();
}
}
} // if (!$table->isReferenceOnly())
}
$phpconfClassmap = array_merge($phpconfClassmap, $classMap);
}
}
return $phpconfClassmap;
}
}

View file

@ -0,0 +1,68 @@
<?php
/**
* This file is part of the Propel package.
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @license MIT License
*/
require_once 'task/PropelDataModelTemplateTask.php';
require_once 'builder/om/ClassTools.php';
/**
* This Task creates the OM classes based on the XML schema file.
*
* @author Hans Lellelid <hans@xmpl.org>
* @package propel.generator.task
*/
class PropelDataDTDTask extends PropelDataModelTemplateTask
{
public function main()
{
// check to make sure task received all correct params
$this->validate();
if (!$this->mapperElement) {
throw new BuildException("You must use a <mapper/> element to describe how names should be transformed.");
}
$basepath = $this->getOutputDirectory();
// Get new Capsule context
$generator = $this->createContext();
$generator->put("basepath", $basepath); // make available to other templates
// we need some values that were loaded into the template context
$basePrefix = $generator->get('basePrefix');
$project = $generator->get('project');
foreach ($this->getDataModels() as $dataModel) {
$this->log("Processing Datamodel : " . $dataModel->getName());
foreach ($dataModel->getDatabases() as $database) {
$outFile = $this->getMappedFile($dataModel->getName());
$generator->put("tables", $database->getTables());
$generator->parse("data/dtd/dataset.tpl", $outFile->getAbsolutePath());
$this->log("Generating DTD for database: " . $database->getName());
$this->log("Creating DTD file: " . $outFile->getPath());
foreach ($database->getTables() as $tbl) {
$this->log("\t + " . $tbl->getName());
$generator->put("table", $tbl);
$generator->parse("data/dtd/table.tpl", $outFile->getAbsolutePath(), true);
}
} // foreach database
} // foreach dataModel
} // main()
}

View file

@ -0,0 +1,354 @@
<?php
/**
* This file is part of the Propel package.
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @license MIT License
*/
/**
* Dumps the contenst of selected databases to XML data dump file.
*
* The generated XML files can have corresponding DTD files generated using the
* PropelDataDTDTask. The results of the data dump can be converted to SQL using
* the PropelDataSQLTask class.
*
* The database may be specified (via 'databaseName' attribute) if you only want to dump
* the contents of one database. Otherwise it is assumed that all databases described
* by datamodel schema file(s) will be dumped.
*
* @author Hans Lellelid <hans@xmpl.org> (Propel)
* @author Fedor Karpelevitch <fedor.karpelevitch@home.com> (Torque)
* @author Jason van Zyl <jvanzyl@zenplex.com> (Torque)
* @author Daniel Rall <dlr@finemaltcoding.com> (Torque)
* @version $Revision: 1612 $
* @package propel.generator.task
*/
class PropelDataDumpTask extends AbstractPropelDataModelTask
{
/**
* Database name.
* The database name may be optionally specified in the XML if you only want
* to dump the contents of one database.
*/
private $databaseName;
/**
* Database URL used for Propel connection.
* This is a PEAR-compatible (loosely) DSN URL.
*/
private $databaseUrl;
/**
* Database driver used for Propel connection.
* This should normally be left blank so that default (Propel built-in) driver for database type is used.
*/
private $databaseDriver;
/**
* Database user used for Propel connection.
* @deprecated Put username in databaseUrl.
*/
private $databaseUser;
/**
* Database password used for Propel connection.
* @deprecated Put password in databaseUrl.
*/
private $databasePassword;
/**
* Properties file that maps a data XML file to a particular database.
* @var PhingFile
*/
private $datadbmap;
/**
* The database connection used to retrieve the data to dump.
* Needs to be public so that the TableInfo class can access it.
*/
public $conn;
/**
* The statement used to acquire the data to dump.
*/
private $stmt;
/**
* Set the file that maps between data XML files and databases.
*
* @param PhingFile $sqldbmap the db map
* @return void
*/
public function setDataDbMap(PhingFile $datadbmap)
{
$this->datadbmap = $datadbmap;
}
/**
* Get the file that maps between data XML files and databases.
*
* @return PhingFile $datadbmap.
*/
public function getDataDbMap()
{
return $this->datadbmap;
}
/**
* Get the database name to dump
*
* @return The DatabaseName value
*/
public function getDatabaseName()
{
return $this->databaseName;
}
/**
* Set the database name
*
* @param v The new DatabaseName value
*/
public function setDatabaseName($v)
{
$this->databaseName = $v;
}
/**
* Get the database url
*
* @return The DatabaseUrl value
*/
public function getDatabaseUrl()
{
return $this->databaseUrl;
}
/**
* Set the database url
*
* @param string $v The PEAR-compatible database DSN URL.
*/
public function setDatabaseUrl($v)
{
$this->databaseUrl = $v;
}
/**
* Get the database user
*
* @return string database user
* @deprecated
*/
public function getDatabaseUser()
{
return $this->databaseUser;
}
/**
* Set the database user
*
* @param string $v The new DatabaseUser value
* @deprecated Specify user in DSN URL.
*/
public function setDatabaseUser($v)
{
$this->databaseUser = $v;
}
/**
* Get the database password
*
* @return string database password
*/
public function getDatabasePassword()
{
return $this->databasePassword;
}
/**
* Set the database password
*
* @param string $v The new DatabasePassword value
* @deprecated Specify database password in DSN URL.
*/
public function setDatabasePassword($v)
{
$this->databasePassword = $v;
}
/**
* Get the database driver name
*
* @return string database driver name
*/
public function getDatabaseDriver()
{
return $this->databaseDriver;
}
/**
* Set the database driver name
*
* @param string $v The new DatabaseDriver value
*/
public function setDatabaseDriver($v)
{
$this->databaseDriver = $v;
}
/**
* Create the data XML -> database map.
*
* This is necessary because there is currently no other method of knowing which
* data XML files correspond to which database. This map allows us to convert multiple
* data XML files into SQL.
*
* @throws IOException - if unable to store properties
*/
private function createDataDbMap()
{
if ($this->getDataDbMap() === null) {
return;
}
// Produce the sql -> database map
$datadbmap = new Properties();
// Check to see if the sqldbmap has already been created.
if ($this->getDataDbMap()->exists()) {
$datadbmap->load($this->getDataDbMap());
}
foreach ($this->getDataModels() as $dataModel) { // there is really one 1 db per datamodel
foreach ($dataModel->getDatabases() as $database) {
// if database name is specified, then we only want to dump that one db.
if (empty($this->databaseName) || ($this->databaseName && $database->getName() == $this->databaseName)) {
$outFile = $this->getMappedFile($dataModel->getName());
$datadbmap->setProperty($outFile->getName(), $database->getName());
}
}
}
try {
$datadbmap->store($this->getDataDbMap(), "Data XML file -> Database map");
} catch (IOException $e) {
throw new IOException("Unable to store properties: ". $e->getMessage());
}
}
/**
* Iterates through each datamodel/database, dumps the contents of all tables and creates a DOM XML doc.
*
* @return void
* @throws BuildException
*/
public function main()
{
$this->validate();
$buf = "Database settings:\n"
. " driver: " . ($this->databaseDriver ? $this->databaseDriver : "(default)" ). "\n"
. " URL: " . $this->databaseUrl . "\n"
. ($this->databaseUser ? " user: " . $this->databaseUser . "\n" : "")
. ($this->databasePassword ? " password: " . $this->databasePassword . "\n" : "");
$this->log($buf, Project::MSG_VERBOSE);
// 1) First create the Data XML -> database name map.
$this->createDataDbMap();
// 2) Now go create the XML files from teh database(s)
foreach ($this->getDataModels() as $dataModel) { // there is really one 1 db per datamodel
foreach ($dataModel->getDatabases() as $database) {
// if database name is specified, then we only want to dump that one db.
if (empty($this->databaseName) || ($this->databaseName && $database->getName() == $this->databaseName)) {
$outFile = $this->getMappedFile($dataModel->getName());
$this->log("Dumping data to XML for database: " . $database->getName());
$this->log("Writing to XML file: " . $outFile->getName());
try {
$url = str_replace("@DB@", $database->getName(), $this->databaseUrl);
if ($url !== $this->databaseUrl) {
$this->log("New (resolved) URL: " . $url, Project::MSG_VERBOSE);
}
if (empty($url)) {
throw new BuildException("Unable to connect to database; no PDO connection URL specified.", $this->getLocation());
}
$this->conn = new PDO($url, $this->databaseUser, $this->databasePassword);
$this->conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$doc = $this->createXMLDoc($database);
$doc->save($outFile->getAbsolutePath());
} catch (SQLException $se) {
$this->log("SQLException while connecting to DB: ". $se->getMessage(), Project::MSG_ERR);
throw new BuildException($se);
}
} // if databaseName && database->getName == databaseName
} // foreach database
} // foreach datamodel
}
/**
* Gets PDOStatement of query to fetch all data from a table.
* @param string $tableName
* @param Platform $platform
* @return PDOStatement
*/
private function getTableDataStmt($tableName, Platform $platform)
{
return $this->conn->query("SELECT * FROM " . $platform->quoteIdentifier( $tableName ) );
}
/**
* Creates a DOM document containing data for specified database.
* @param Database $database
* @return DOMDocument
*/
private function createXMLDoc(Database $database)
{
$doc = new DOMDocument('1.0', 'utf-8');
$doc->formatOutput = true; // pretty printing
$doc->appendChild($doc->createComment("Created by data/dump/Control.tpl template."));
$dsNode = $doc->createElement("dataset");
$dsNode->setAttribute("name", "all");
$doc->appendChild($dsNode);
$platform = $this->getGeneratorConfig()->getConfiguredPlatform($this->conn);
$this->log("Building DOM tree containing data from tables:");
foreach ($database->getTables() as $tbl) {
$this->log("\t+ " . $tbl->getName());
$stmt = $this->getTableDataStmt($tbl->getName(), $platform);
while ($row = $stmt->fetch()) {
$rowNode = $doc->createElement($tbl->getPhpName());
foreach ($tbl->getColumns() as $col) {
$cval = $row[$col->getName()];
if ($cval !== null) {
$rowNode->setAttribute($col->getPhpName(), iconv($this->dbEncoding, 'utf-8', $cval));
}
}
$dsNode->appendChild($rowNode);
unset($rowNode);
}
unset($stmt);
}
return $doc;
}
}

View file

@ -0,0 +1,217 @@
<?php
/**
* This file is part of the Propel package.
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @license MIT License
*/
require_once 'task/AbstractPropelDataModelTask.php';
include_once 'model/AppData.php';
include_once 'model/Database.php';
include_once 'builder/util/XmlToAppData.php';
/**
* A generic class that simply loads the data model and parses a control template.
*
* This class exists largely for compatibility with early Propel where this was
* a CapsuleTask subclass. This class also makes it easy to quickly add some custom
* datamodel-based transformations (by allowing you to put the logic in the templates).
*
* @author Hans Lellelid <hans@xmpl.org>
* @package propel.generator.task
* @version $Revision: 1612 $
*/
class PropelDataModelTemplateTask extends AbstractPropelDataModelTask
{
/**
* This is the file where the generated text
* will be placed.
* @var string
*/
protected $outputFile;
/**
* Path where Capsule looks for templates.
* @var PhingFile
*/
protected $templatePath;
/**
* This is the control template that governs the output.
* It may or may not invoke the services of worker
* templates.
* @var string
*/
protected $controlTemplate;
/**
* [REQUIRED] Set the output file for the
* generation process.
* @param string $outputFile (TODO: change this to File)
* @return void
*/
public function setOutputFile($outputFile) {
$this->outputFile = $outputFile;
}
/**
* Get the output file for the
* generation process.
* @return string
*/
public function getOutputFile() {
return $this->outputFile;
}
/**
* [REQUIRED] Set the control template for the
* generating process.
* @param string $controlTemplate
* @return void
*/
public function setControlTemplate ($controlTemplate) {
$this->controlTemplate = $controlTemplate;
}
/**
* Get the control template for the
* generating process.
* @return string
*/
public function getControlTemplate() {
return $this->controlTemplate;
}
/**
* [REQUIRED] Set the path where Capsule will look
* for templates using the file template
* loader.
* @return void
* @throws Exception
*/
public function setTemplatePath($templatePath) {
$resolvedPath = "";
$tok = strtok($templatePath, ",");
while ( $tok ) {
// resolve relative path from basedir and leave
// absolute path untouched.
$fullPath = $this->project->resolveFile($tok);
$cpath = $fullPath->getCanonicalPath();
if ($cpath === false) {
$this->log("Template directory does not exist: " . $fullPath->getAbsolutePath());
} else {
$resolvedPath .= $cpath;
}
$tok = strtok(",");
if ( $tok ) {
$resolvedPath .= ",";
}
}
$this->templatePath = $resolvedPath;
}
/**
* Get the path where Velocity will look
* for templates using the file template
* loader.
* @return string
*/
public function getTemplatePath() {
return $this->templatePath;
}
/**
* Creates a new Capsule context with some basic properties set.
* (Capsule is a simple PHP encapsulation system -- aka a php "template" class.)
* @return Capsule
*/
protected function createContext() {
$context = new Capsule();
// Make sure the output directory exists, if it doesn't
// then create it.
$outputDir = new PhingFile($this->outputDirectory);
if (!$outputDir->exists()) {
$this->log("Output directory does not exist, creating: " . $outputDir->getAbsolutePath());
$outputDir->mkdirs();
}
// Place our set of data models into the context along
// with the names of the databases as a convenience for now.
$context->put("targetDatabase", $this->getTargetDatabase());
$context->put("targetPackage", $this->getTargetPackage());
$context->put("now", strftime("%c"));
$this->log("Target database type: " . $this->getTargetDatabase());
$this->log("Target package: " . $this->getTargetPackage());
$this->log("Using template path: " . $this->templatePath);
$this->log("Output directory: " . $this->getOutputDirectory());
$context->setTemplatePath($this->templatePath);
$context->setOutputDirectory($this->outputDirectory);
$this->populateContextProperties($context);
return $context;
}
/**
* Adds the propel build properties to the passed Capsule context.
*
* @param Capsule $context
* @see GeneratorConfig::getBuildProperties()
*/
public function populateContextProperties(Capsule $context)
{
foreach ($this->getGeneratorConfig()->getBuildProperties() as $key => $propValue) {
$this->log('Adding property ${' . $key . '} to context', Project::MSG_DEBUG);
$context->put($key, $propValue);
}
}
/**
* Performs validation for single-file mode.
* @throws BuildException - if there are any validation errors
*/
protected function singleFileValidate()
{
parent::validate();
// Make sure the control template is set.
if ($this->controlTemplate === null) {
throw new BuildException("The control template needs to be defined!");
}
// Make sure there is an output file.
if ($this->outputFile === null) {
throw new BuildException("The output file needs to be defined!");
}
}
/**
* Creates Capsule context and parses control template.
* @return void
*/
public function main()
{
$this->singleFileValidate();
$context = $this->createContext();
$context->put("dataModels", $this->getDataModels());
$path = $this->outputDirectory . DIRECTORY_SEPARATOR . $this->outputFile;
$this->log("Generating to file " . $path);
try {
$this->log("Parsing control template: " . $this->controlTemplate);
$context->parse($this->controlTemplate, $path);
} catch (Exception $ioe) {
throw new BuildException("Cannot write parsed template: ". $ioe->getMessage());
}
}
}

View file

@ -0,0 +1,193 @@
<?php
/**
* This file is part of the Propel package.
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @license MIT License
*/
require_once 'model/AppData.php';
require_once 'model/Database.php';
require_once 'builder/util/XmlToAppData.php';
require_once 'builder/util/XmlToDataSQL.php';
/**
* Task that transforms XML datadump files into files containing SQL INSERT statements.
*
* @author Hans Lellelid <hans@xmpl.org> (Propel)
* @author Jason van Zyl <jvanzyl@periapt.com> (Torque)
* @author John McNally <jmcnally@collab.net> (Torque)
* @author Fedor Karpelevitch <fedor.karpelevitch@home.com> (Torque)
* @version $Revision: 1612 $
* @package propel.generator.task
*/
class PropelDataSQLTask extends AbstractPropelDataModelTask
{
/**
* Properties file that maps an SQL file to a particular database.
* @var PhingFile
*/
private $sqldbmap;
/**
* Properties file that maps a data XML file to a particular database.
* @var PhingFile
*/
private $datadbmap;
/**
* The base directory in which to find data XML files.
* @var PhingFile
*/
private $srcDir;
/**
* Set the file that maps between SQL files and databases.
*
* @param PhingFile $sqldbmap the sql -> db map.
* @return void
*/
public function setSqlDbMap(PhingFile $sqldbmap)
{
$this->sqldbmap = $sqldbmap;
}
/**
* Get the file that maps between SQL files and databases.
*
* @return PhingFile sqldbmap.
*/
public function getSqlDbMap()
{
return $this->sqldbmap;
}
/**
* Set the file that maps between data XML files and databases.
*
* @param PhingFile $sqldbmap the db map
* @return void
*/
public function setDataDbMap(PhingFile $datadbmap)
{
$this->datadbmap = $datadbmap;
}
/**
* Get the file that maps between data XML files and databases.
*
* @return PhingFile $datadbmap.
*/
public function getDataDbMap()
{
return $this->datadbmap;
}
/**
* Set the src directory for the data xml files listed in the datadbmap file.
* @param PhingFile $srcDir data xml source directory
*/
public function setSrcDir(PhingFile $srcDir)
{
$this->srcDir = $srcDir;
}
/**
* Get the src directory for the data xml files listed in the datadbmap file.
*
* @return PhingFile data xml source directory
*/
public function getSrcDir()
{
return $this->srcDir;
}
/**
* Search through all data models looking for matching database.
* @return Database or NULL if none found.
*/
private function getDatabase($name)
{
foreach ($this->getDataModels() as $dm) {
foreach ($dm->getDatabases() as $db) {
if ($db->getName() == $name) {
return $db;
}
}
}
}
/**
* Main method parses the XML files and creates SQL files.
*
* @return void
* @throws Exception If there is an error parsing the data xml.
*/
public function main()
{
$this->validate();
$targetDatabase = $this->getTargetDatabase();
$platform = $this->getGeneratorConfig()->getConfiguredPlatform();
// Load the Data XML -> DB Name properties
$map = new Properties();
try {
$map->load($this->getDataDbMap());
} catch (IOException $ioe) {
throw new BuildException("Cannot open and process the datadbmap!", $ioe);
}
// Parse each file in the data -> db map
foreach ($map->keys() as $dataXMLFilename) {
$dataXMLFile = new PhingFile($this->srcDir, $dataXMLFilename);
// if file exists then proceed
if ($dataXMLFile->exists()) {
$dbname = $map->get($dataXMLFilename);
$db = $this->getDatabase($dbname);
if (!$db) {
throw new BuildException("Cannot find instantiated Database for name '$dbname' from datadbmap file.");
}
$db->setPlatform($platform);
$outFile = $this->getMappedFile($dataXMLFilename);
$sqlWriter = new FileWriter($outFile);
$this->log("Creating SQL from XML data dump file: " . $dataXMLFile->getAbsolutePath());
try {
$dataXmlParser = new XmlToDataSQL($db, $this->getGeneratorConfig(), $this->dbEncoding);
$dataXmlParser->transform($dataXMLFile, $sqlWriter);
} catch (Exception $e) {
throw new BuildException("Exception parsing data XML: " . $e->getMessage(), $x);
}
// Place the generated SQL file(s)
$p = new Properties();
if ($this->getSqlDbMap()->exists()) {
$p->load($this->getSqlDbMap());
}
$p->setProperty($outFile->getName(), $db->getName());
$p->store($this->getSqlDbMap(), "Sqlfile -> Database map");
} else {
$this->log("File '" . $dataXMLFile->getAbsolutePath()
. "' in datadbmap does not exist, so skipping it.", Project::MSG_WARN);
}
} // foreach data xml file
} // main()
}

View file

@ -0,0 +1,170 @@
<?php
/**
* This file is part of the Propel package.
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @license MIT License
*/
require_once 'task/AbstractPropelDataModelTask.php';
require_once 'model/AppData.php';
/**
* A task to generate Graphviz dot files from Propel datamodel.
*
* @author Mark Kimsal
* @version $Revision: 1612 $
* @package propel.generator.task
*/
class PropelGraphvizTask extends AbstractPropelDataModelTask
{
/**
* The properties file that maps an SQL file to a particular database.
* @var PhingFile
*/
private $sqldbmap;
/**
* Name of the database.
*/
private $database;
/**
* Name of the output directory.
*/
private $outDir;
/**
* Set the sqldbmap.
* @param PhingFile $sqldbmap The db map.
*/
public function setOutputDirectory(PhingFile $out)
{
if (!$out->exists()) {
$out->mkdirs();
}
$this->outDir = $out;
}
/**
* Set the sqldbmap.
* @param PhingFile $sqldbmap The db map.
*/
public function setSqlDbMap(PhingFile $sqldbmap)
{
$this->sqldbmap = $sqldbmap;
}
/**
* Get the sqldbmap.
* @return PhingFile $sqldbmap.
*/
public function getSqlDbMap()
{
return $this->sqldbmap;
}
/**
* Set the database name.
* @param string $database
*/
public function setDatabase($database)
{
$this->database = $database;
}
/**
* Get the database name.
* @return string
*/
public function getDatabase()
{
return $this->database;
}
public function main()
{
$count = 0;
$dotSyntax = '';
// file we are going to create
$dbMaps = $this->getDataModelDbMap();
foreach ($this->getDataModels() as $dataModel) {
$dotSyntax .= "digraph G {\n";
foreach ($dataModel->getDatabases() as $database) {
$this->log("db: " . $database->getName());
//print the tables
foreach ($database->getTables() as $tbl) {
$this->log("\t+ " . $tbl->getName());
++$count;
$dotSyntax .= 'node'.$tbl->getName().' [label="{<table>'.$tbl->getName().'|<cols>';
foreach ($tbl->getColumns() as $col) {
$dotSyntax .= $col->getName() . ' (' . $col->getType() . ')';
if (count($col->getForeignKeys()) > 0) {
$dotSyntax .= ' [FK]';
} elseif ($col->isPrimaryKey()) {
$dotSyntax .= ' [PK]';
}
$dotSyntax .= '\l';
}
$dotSyntax .= '}", shape=record];';
$dotSyntax .= "\n";
}
//print the relations
$count = 0;
$dotSyntax .= "\n";
foreach ($database->getTables() as $tbl) {
++$count;
foreach ($tbl->getColumns() as $col) {
$fk = $col->getForeignKeys();
if ( count($fk) == 0 or $fk === null ) continue;
if ( count($fk) > 1 ) throw( new Exception("not sure what to do here...") );
$fk = $fk[0]; // try first one
$dotSyntax .= 'node'.$tbl->getName() .':cols -> node'.$fk->getForeignTableName() . ':table [label="' . $col->getName() . '=' . implode(',', $fk->getForeignColumns()) . ' "];';
$dotSyntax .= "\n";
}
}
} // foreach database
$dotSyntax .= "}\n";
$this->writeDot($dotSyntax,$this->outDir,$database->getName());
$dotSyntax = '';
} //foreach datamodels
} // main()
/**
* probably insecure
*/
function writeDot($dotSyntax, PhingFile $outputDir, $baseFilename) {
$file = new PhingFile($outputDir, $baseFilename . '.schema.dot');
$this->log("Writing dot file to " . $file->getAbsolutePath());
file_put_contents($file->getAbsolutePath(), $dotSyntax);
}
}

View file

@ -0,0 +1,231 @@
<?php
/**
* This file is part of the Propel package.
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @license MIT License
*/
require_once 'task/AbstractPropelDataModelTask.php';
require_once 'builder/om/ClassTools.php';
require_once 'builder/om/OMBuilder.php';
/**
* This Task creates the OM classes based on the XML schema file.
*
* @author Hans Lellelid <hans@xmpl.org>
* @package propel.generator.task
*/
class PropelOMTask extends AbstractPropelDataModelTask
{
/**
* The platform (php4, php5, etc.) for which the om is being built.
* @var string
*/
private $targetPlatform;
/**
* Sets the platform (php4, php5, etc.) for which the om is being built.
* @param string $v
*/
public function setTargetPlatform($v) {
$this->targetPlatform = $v;
}
/**
* Gets the platform (php4, php5, etc.) for which the om is being built.
* @return string
*/
public function getTargetPlatform() {
return $this->targetPlatform;
}
/**
* Utility method to create directory for package if it doesn't already exist.
* @param string $path The [relative] package path.
* @throws BuildException - if there is an error creating directories
*/
protected function ensureDirExists($path)
{
$f = new PhingFile($this->getOutputDirectory(), $path);
if (!$f->exists()) {
if (!$f->mkdirs()) {
throw new BuildException("Error creating directories: ". $f->getPath());
}
}
}
/**
* Uses a builder class to create the output class.
* This method assumes that the DataModelBuilder class has been initialized with the build properties.
* @param OMBuilder $builder
* @param boolean $overwrite Whether to overwrite existing files with te new ones (default is YES).
* @todo -cPropelOMTask Consider refactoring build() method into AbstractPropelDataModelTask (would need to be more generic).
*/
protected function build(OMBuilder $builder, $overwrite = true)
{
$path = $builder->getClassFilePath();
$this->ensureDirExists(dirname($path));
$_f = new PhingFile($this->getOutputDirectory(), $path);
// skip files already created once
if ($_f->exists() && !$overwrite) {
$this->log("\t\t-> (exists) " . $builder->getClassname(), Project::MSG_VERBOSE);
return 0;
}
$script = $builder->build();
foreach ($builder->getWarnings() as $warning) {
$this->log($warning, Project::MSG_WARN);
}
// skip unchanged files
if ($_f->exists() && $script == $_f->contents()) {
$this->log("\t\t-> (unchanged) " . $builder->getClassname(), Project::MSG_VERBOSE);
return 0;
}
// write / overwrite new / changed files
$this->log("\t\t-> " . $builder->getClassname() . " [builder: " . get_class($builder) . "]");
file_put_contents($_f->getAbsolutePath(), $script);
return 1;
}
/**
* Main method builds all the targets for a typical propel project.
*/
public function main()
{
// check to make sure task received all correct params
$this->validate();
$generatorConfig = $this->getGeneratorConfig();
$totalNbFiles = 0;
foreach ($this->getDataModels() as $dataModel) {
$this->log("Processing Datamodel : " . $dataModel->getName());
foreach ($dataModel->getDatabases() as $database) {
$this->log(" - processing database : " . $database->getName());
foreach ($database->getTables() as $table) {
if (!$table->isForReferenceOnly()) {
$nbWrittenFiles = 0;
$this->log("\t+ " . $table->getName());
// -----------------------------------------------------------------------------------------
// Create Peer, Object, and TableMap classes
// -----------------------------------------------------------------------------------------
// these files are always created / overwrite any existing files
foreach (array('peer', 'object', 'tablemap', 'query') as $target) {
$builder = $generatorConfig->getConfiguredBuilder($table, $target);
$nbWrittenFiles += $this->build($builder);
}
// -----------------------------------------------------------------------------------------
// Create [empty] stub Peer and Object classes if they don't exist
// -----------------------------------------------------------------------------------------
// these classes are only generated if they don't already exist
foreach (array('peerstub', 'objectstub', 'querystub') as $target) {
$builder = $generatorConfig->getConfiguredBuilder($table, $target);
$nbWrittenFiles += $this->build($builder, $overwrite=false);
}
// -----------------------------------------------------------------------------------------
// Create [empty] stub child Object classes if they don't exist
// -----------------------------------------------------------------------------------------
// If table has enumerated children (uses inheritance) then create the empty child stub classes if they don't already exist.
if ($table->getChildrenColumn()) {
$col = $table->getChildrenColumn();
if ($col->isEnumeratedClasses()) {
foreach ($col->getChildren() as $child) {
foreach (array('queryinheritance') as $target) {
if (!$child->getAncestor()) {
continue;
}
$builder = $generatorConfig->getConfiguredBuilder($table, $target);
$builder->setChild($child);
$nbWrittenFiles += $this->build($builder, $overwrite=true);
}
foreach (array('objectmultiextend', 'queryinheritancestub') as $target) {
$builder = $generatorConfig->getConfiguredBuilder($table, $target);
$builder->setChild($child);
$nbWrittenFiles += $this->build($builder, $overwrite=false);
}
} // foreach
} // if col->is enumerated
} // if tbl->getChildrenCol
// -----------------------------------------------------------------------------------------
// Create [empty] Interface if it doesn't exist
// -----------------------------------------------------------------------------------------
// Create [empty] interface if it does not already exist
if ($table->getInterface()) {
$builder = $generatorConfig->getConfiguredBuilder($table, 'interface');
$nbWrittenFiles += $this->build($builder, $overwrite=false);
}
// -----------------------------------------------------------------------------------------
// Create tree Node classes
// -----------------------------------------------------------------------------------------
if ($table->treeMode()) {
switch($table->treeMode()) {
case 'NestedSet':
foreach (array('nestedsetpeer', 'nestedset') as $target) {
$builder = $generatorConfig->getConfiguredBuilder($table, $target);
$nbWrittenFiles += $this->build($builder);
}
break;
case 'MaterializedPath':
foreach (array('nodepeer', 'node') as $target) {
$builder = $generatorConfig->getConfiguredBuilder($table, $target);
$nbWrittenFiles += $this->build($builder);
}
foreach (array('nodepeerstub', 'nodestub') as $target) {
$builder = $generatorConfig->getConfiguredBuilder($table, $target);
$nbWrittenFiles += $this->build($builder, $overwrite=false);
}
break;
case 'AdjacencyList':
// No implementation for this yet.
default:
break;
}
} // if Table->treeMode()
$totalNbFiles += $nbWrittenFiles;
if ($nbWrittenFiles == 0) {
$this->log("\t\t(no change)");
}
} // if !$table->isForReferenceOnly()
} // foreach table
} // foreach database
} // foreach dataModel
if ($totalNbFiles) {
$this->log(sprintf("Object model generation complete - %d files written", $totalNbFiles));
} else {
$this->log("Object model generation complete - All files already up to date");
}
} // main()
}

View file

@ -0,0 +1,701 @@
<?php
/**
* This file is part of the Propel package.
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @license MIT License
*/
require_once 'phing/Task.php';
/**
* Executes all SQL files referenced in the sqldbmap file against their mapped databases.
*
* This task uses an SQL -> Database map in the form of a properties
* file to insert each SQL file listed into its designated database.
*
* @author Hans Lellelid <hans@xmpl.org>
* @author Dominik del Bondio
* @author Jeff Martin <jeff@custommonkey.org> (Torque)
* @author Michael McCallum <gholam@xtra.co.nz> (Torque)
* @author Tim Stephenson <tim.stephenson@sybase.com> (Torque)
* @author Jason van Zyl <jvanzyl@apache.org> (Torque)
* @author Martin Poeschl <mpoeschl@marmot.at> (Torque)
* @version $Revision: 1612 $
* @package propel.generator.task
*/
class PropelSQLExec extends Task
{
private $goodSql = 0;
private $totalSql = 0;
const DELIM_ROW = "row";
const DELIM_NORMAL = "normal";
/**
* The delimiter type indicating whether the delimiter will
* only be recognized on a line by itself
*/
private $delimiterType = "normal"; // can't use constant just defined
//private static $delimiterTypes = array(DELIM_NORMAL, DELIM_ROW);
//private static $errorActions = array("continue", "stop", "abort");
/** PDO Database connection */
private $conn = null;
/** Autocommit flag. Default value is false */
private $autocommit = false;
/** DB url. */
private $url = null;
/** User name. */
private $userId = null;
/** Password */
private $password = null;
/** SQL input command */
private $sqlCommand = "";
/** SQL transactions to perform */
private $transactions = array();
/** SQL Statement delimiter */
private $delimiter = ";";
/** Print SQL results. */
private $print = false;
/** Print header columns. */
private $showheaders = true;
/** Results Output file. */
private $output = null;
/** RDBMS Product needed for this SQL. */
private $rdbms = null;
/** RDBMS Version needed for this SQL. */
private $version = null;
/** Action to perform if an error is found */
private $onError = "abort";
/** Encoding to use when reading SQL statements from a file */
private $encoding = null;
/** Src directory for the files listed in the sqldbmap. */
private $srcDir;
/** Properties file that maps an individual SQL file to a database. */
private $sqldbmap;
/**
* Set the sqldbmap properties file.
*
* @param sqldbmap filename for the sqldbmap
*/
public function setSqlDbMap($sqldbmap)
{
$this->sqldbmap = $this->project->resolveFile($sqldbmap);
}
/**
* Get the sqldbmap properties file.
*
* @return filename for the sqldbmap
*/
public function getSqlDbMap()
{
return $this->sqldbmap;
}
/**
* Set the src directory for the sql files listed in the sqldbmap file.
*
* @param PhingFile $srcDir sql source directory
*/
public function setSrcDir(PhingFile $srcDir)
{
$this->srcDir = $srcDir;
}
/**
* Get the src directory for the sql files listed in the sqldbmap file.
*
* @return PhingFile SQL Source directory
*/
public function getSrcDir()
{
return $this->srcDir;
}
/**
* Set the sql command to execute
*
* @param sql sql command to execute
*/
public function addText($sql)
{
$this->sqlCommand .= $sql;
}
/**
* Set the DB connection url.
*
* @param string $url connection url
*/
public function setUrl($url)
{
$this->url = $url;
}
/**
* Set the user name for the DB connection.
*
* @param string $userId database user
* @deprecated Specify userid in the DSN URL.
*/
public function setUserid($userId)
{
$this->userId = $userId;
}
/**
* Set the password for the DB connection.
*
* @param string $password database password
* @deprecated Specify password in the DSN URL.
*/
public function setPassword($password)
{
$this->password = $password;
}
/**
* Set the autocommit flag for the DB connection.
*
* @param boolean $autocommit the autocommit flag
*/
public function setAutoCommit($autocommit)
{
$this->autocommit = (boolean) $autocommit;
}
/**
* Set the statement delimiter.
*
* <p>For example, set this to "go" and delimitertype to "ROW" for
* Sybase ASE or MS SQL Server.</p>
*
* @param string $delimiter
*/
public function setDelimiter($delimiter)
{
$this->delimiter = $delimiter;
}
/**
* Set the Delimiter type for this sql task. The delimiter type takes two
* values - normal and row. Normal means that any occurence of the delimiter
* terminate the SQL command whereas with row, only a line containing just
* the delimiter is recognized as the end of the command.
*
* @param string $delimiterType
*/
public function setDelimiterType($delimiterType)
{
$this->delimiterType = $delimiterType;
}
/**
* Set the print flag.
*
* @param boolean $print
*/
public function setPrint($print)
{
$this->print = (boolean) $print;
}
/**
* Set the showheaders flag.
*
* @param boolean $showheaders
*/
public function setShowheaders($showheaders)
{
$this->showheaders = (boolean) $showheaders;
}
/**
* Set the output file.
*
* @param PhingFile $output
*/
public function setOutput(PhingFile $output)
{
$this->output = $output;
}
/**
* Set the action to perform onerror
*
* @param string $action
*/
public function setOnerror($action)
{
$this->onError = $action;
}
/**
* Load the sql file and then execute it
*
* @throws BuildException
*/
public function main()
{
$this->sqlCommand = trim($this->sqlCommand);
if ($this->sqldbmap === null || $this->getSqlDbMap()->exists() === false) {
throw new BuildException("You haven't provided an sqldbmap, or "
. "the one you specified doesn't exist: " . $this->sqldbmap->getPath());
}
if ($this->url === null) {
throw new BuildException("DSN url attribute must be set!");
}
$map = new Properties();
try {
$map->load($this->getSqlDbMap());
} catch (IOException $ioe) {
throw new BuildException("Cannot open and process the sqldbmap!");
}
$databases = array();
foreach ($map->keys() as $sqlfile) {
$database = $map->getProperty($sqlfile);
// Q: already there?
if (!isset($databases[$database])) {
// A: No.
$databases[$database] = array();
}
// We want to make sure that the base schemas
// are inserted first.
if (strpos($sqlfile, "schema.sql") !== false) {
// add to the beginning of the array
array_unshift($databases[$database], $sqlfile);
} else {
array_push($databases[$database], $sqlfile);
}
}
foreach ($databases as $db => $files) {
$transactions = array();
foreach ($files as $fileName) {
$file = new PhingFile($this->srcDir, $fileName);
if ($file->exists()) {
$this->log("Executing statements in file: " . $file->__toString());
$transaction = new PropelSQLExecTransaction($this);
$transaction->setSrc($file);
$transactions[] = $transaction;
} else {
$this->log("File '" . $file->__toString()
. "' in sqldbmap does not exist, so skipping it.");
}
}
$this->insertDatabaseSqlFiles($this->url, $db, $transactions);
}
}
/**
* Take the base url, the target database and insert a set of SQL
* files into the target database.
*
* @param string $url
* @param string $database
* @param array $transactions
*/
private function insertDatabaseSqlFiles($url, $database, $transactions)
{
$url = str_replace("@DB@", $database, $url);
$this->log("Our new url -> " . $url);
try {
$buf = "Database settings:" . PHP_EOL
. " URL: " . $url . PHP_EOL
. ($this->userId ? " user: " . $this->userId . PHP_EOL : "")
. ($this->password ? " password: " . $this->password . PHP_EOL : "");
$this->log($buf, Project::MSG_VERBOSE);
// Set user + password to null if they are empty strings
if (!$this->userId) { $this->userId = null; }
if (!$this->password) { $this->password = null; }
$this->conn = new PDO($url, $this->userId, $this->password);
$this->conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
// $this->conn->setAutoCommit($this->autocommit);
// $this->statement = $this->conn->createStatement();
$out = null;
try {
if ($this->output !== null) {
$this->log("Opening PrintStream to output file " . $this->output->__toString(), Project::MSG_VERBOSE);
$out = new FileWriter($this->output);
}
// Process all transactions
for ($i=0,$size=count($transactions); $i < $size; $i++) {
$transactions[$i]->runTransaction($out);
if (!$this->autocommit) {
$this->log("Commiting transaction", Project::MSG_VERBOSE);
$this->conn->commit();
}
}
} catch (Exception $e) {
if ($out) $out->close();
}
} catch (IOException $e) {
if (!$this->autocommit && $this->conn !== null && $this->onError == "abort") {
try {
$this->conn->rollBack();
} catch (PDOException $ex) {
// do nothing.
System::println("Rollback failed.");
}
}
if ($this->statement) $this->statement = null; // close
throw new BuildException($e);
} catch (PDOException $e) {
if (!$this->autocommit && $this->conn !== null && $this->onError == "abort") {
try {
$this->conn->rollBack();
} catch (PDOException $ex) {
// do nothing.
System::println("Rollback failed");
}
}
if ($this->statement) $this->statement = null; // close
throw new BuildException($e);
}
$this->statement = null; // close
$this->log($this->goodSql . " of " . $this->totalSql
. " SQL statements executed successfully");
}
/**
* Read the statements from the .sql file and execute them.
* Lines starting with '//', '--' or 'REM ' are ignored.
*
* Developer note: must be public in order to be called from
* sudo-"inner" class PropelSQLExecTransaction.
*
* @param Reader $reader
* @param $out Optional output stream.
* @throws PDOException
* @throws IOException
*/
public function runStatements(Reader $reader, $out = null)
{
$sql = "";
$line = "";
$sqlBacklog = "";
$hasQuery = false;
$in = new BufferedReader($reader);
$parser['pointer'] = 0;
$parser['isInString'] = false;
$parser['stringQuotes'] = "";
$parser['backslashCount'] = 0;
$parser['parsedString'] = "";
$sqlParts = array();
while (($line = $in->readLine()) !== null) {
$line = trim($line);
$line = ProjectConfigurator::replaceProperties($this->project, $line,
$this->project->getProperties());
if (StringHelper::startsWith("//", $line)
|| StringHelper::startsWith("--", $line)
|| StringHelper::startsWith("#", $line)) {
continue;
}
if (strlen($line) > 4 && strtoupper(substr($line,0, 4)) == "REM ") {
continue;
}
if ($sqlBacklog !== "") {
$sql = $sqlBacklog;
$sqlBacklog = "";
}
$sql .= " " . $line . PHP_EOL;
// SQL defines "--" as a comment to EOL
// and in Oracle it may contain a hint
// so we cannot just remove it, instead we must end it
if (strpos($line, "--") !== false) {
$sql .= PHP_EOL;
}
// DELIM_ROW doesn't need this (as far as i can tell)
if ($this->delimiterType == self::DELIM_NORMAL) {
// old regex, being replaced due to segfaults:
// See: http://propel.phpdb.org/trac/ticket/294
//$reg = "#((?:\"(?:\\\\.|[^\"])*\"?)+|'(?:\\\\.|[^'])*'?|" . preg_quote($this->delimiter) . ")#";
//$sqlParts = preg_split($reg, $sql, 0, PREG_SPLIT_DELIM_CAPTURE);
$i = $parser['pointer'];
$c = strlen($sql);
while ($i < $c) {
$char = $sql[$i];
switch($char) {
case "\\":
$parser['backslashCount']++;
$this->log("c$i: found ".$parser['backslashCount']." backslash(es)", Project::MSG_VERBOSE);
break;
case "'":
case "\"":
if ($parser['isInString'] && $parser['stringQuotes'] == $char) {
if (($parser['backslashCount'] & 1) == 0) {
#$this->log("$i: out of string", Project::MSG_VERBOSE);
$parser['isInString'] = false;
} else {
$this->log("c$i: rejected quoted delimiter", Project::MSG_VERBOSE);
}
} elseif (!$parser['isInString']) {
$parser['stringQuotes'] = $char;
$parser['isInString'] = true;
#$this->log("$i: into string with $parser['stringQuotes']", Project::MSG_VERBOSE);
}
break;
}
if ($char == $this->delimiter && !$parser['isInString']) {
$this->log("c$i: valid end of command found!", Project::MSG_VERBOSE);
$sqlParts[] = $parser['parsedString'];
$sqlParts[] = $this->delimiter;
break;
}
$parser['parsedString'] .= $char;
if ($char !== "\\") {
if ($parser['backslashCount']) $this->log("$i: backslash reset", Project::MSG_VERBOSE);
$parser['backslashCount'] = 0;
}
$i++;
$parser['pointer']++;
}
$sqlBacklog = "";
foreach ($sqlParts as $sqlPart) {
// we always want to append, even if it's a delim (which will be stripped off later)
$sqlBacklog .= $sqlPart;
// we found a single (not enclosed by ' or ") delimiter, so we can use all stuff before the delim as the actual query
if ($sqlPart === $this->delimiter) {
$sql = $sqlBacklog;
$sqlBacklog = "";
$hasQuery = true;
}
}
}
if ($hasQuery || ($this->delimiterType == self::DELIM_ROW && $line == $this->delimiter)) {
// this assumes there is always a delimter on the end of the SQL statement.
$sql = StringHelper::substring($sql, 0, strlen($sql) - 1 - strlen($this->delimiter));
$this->log("SQL: " . $sql, Project::MSG_VERBOSE);
$this->execSQL($sql, $out);
$sql = "";
$hasQuery = false;
$parser['pointer'] = 0;
$parser['isInString'] = false;
$parser['stringQuotes'] = "";
$parser['backslashCount'] = 0;
$parser['parsedString'] = "";
$sqlParts = array();
}
}
// Catch any statements not followed by ;
if ($sql !== "") {
$this->execSQL($sql, $out);
}
}
/**
* Exec the sql statement.
*
* @param sql
* @param out
* @throws PDOException
*/
protected function execSQL($sql, $out = null)
{
// Check and ignore empty statements
if (trim($sql) == "") {
return;
}
try {
$this->totalSql++;
if (!$this->autocommit) $this->conn->beginTransaction();
$stmt = $this->conn->prepare($sql);
$stmt->execute();
$this->log($stmt->rowCount() . " rows affected", Project::MSG_VERBOSE);
if (!$this->autocommit) $this->conn->commit();
$this->goodSql++;
} catch (PDOException $e) {
$this->log("Failed to execute: " . $sql, Project::MSG_ERR);
if ($this->onError != "continue") {
throw $e;
}
$this->log($e->getMessage(), Project::MSG_ERR);
}
}
/**
* print any results in the statement.
*
* @param out
* @throws PDOException
*/
protected function printResults($out = null)
{
$rs = null;
do {
$rs = $this->statement->getResultSet();
if ($rs !== null) {
$this->log("Processing new result set.", Project::MSG_VERBOSE);
$line = "";
$colsprinted = false;
while ($rs->next()) {
if (!$colsprinted && $this->showheaders) {
$first = true;
foreach ($this->fields as $fieldName => $ignore) {
if ($first) $first = false; else $line .= ",";
$line .= $fieldName;
}
} // if show headers
$first = true;
foreach ($rs->fields as $columnValue) {
if ($columnValue != null) {
$columnValue = trim($columnValue);
}
if ($first) {
$first = false;
} else {
$line .= ",";
}
$line .= $columnValue;
}
if ($out !== null) {
$out->write($line);
$out->newLine();
}
System::println($line);
$line = "";
} // while rs->next()
}
} while ($this->statement->getMoreResults());
System::println();
if ($out !== null) $out->newLine();
}
}
/**
* "Inner" class that contains the definition of a new transaction element.
* Transactions allow several files or blocks of statements
* to be executed using the same Propel connection and commit
* operation in between.
* @package propel.generator.task
*/
class PropelSQLExecTransaction
{
private $tSrcFile = null;
private $tSqlCommand = "";
private $parent;
function __construct($parent)
{
// Parent is required so that we can log things ...
$this->parent = $parent;
}
public function setSrc(PhingFile $src)
{
$this->tSrcFile = $src;
}
public function addText($sql)
{
$this->tSqlCommand .= $sql;
}
/**
* @throws IOException, PDOException
*/
public function runTransaction($out = null)
{
if (!empty($this->tSqlCommand)) {
$this->parent->log("Executing commands", Project::MSG_INFO);
$this->parent->runStatements($this->tSqlCommand, $out);
}
if ($this->tSrcFile !== null) {
$this->parent->log("Executing file: " . $this->tSrcFile->getAbsolutePath(), Project::MSG_INFO);
$reader = new FileReader($this->tSrcFile);
$this->parent->runStatements($reader, $out);
$reader->close();
}
}
}

View file

@ -0,0 +1,250 @@
<?php
/**
* This file is part of the Propel package.
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @license MIT License
*/
require_once 'model/AppData.php';
/**
* The task for building SQL DDL based on the XML datamodel.
*
* This class uses the new DDLBuilder classes instead of the Capsule PHP templates.
*
* @author Hans Lellelid <hans@xmpl.org>
* @package propel.generator.task
*/
class PropelSQLTask extends AbstractPropelDataModelTask
{
/**
* The properties file that maps an SQL file to a particular database.
* @var PhingFile
*/
private $sqldbmap;
/**
* Name of the database.
*/
private $database;
/**
* Set the sqldbmap.
* @param PhingFile $sqldbmap The db map.
*/
public function setSqlDbMap(PhingFile $sqldbmap)
{
$this->sqldbmap = $sqldbmap;
}
/**
* Get the sqldbmap.
* @return PhingFile $sqldbmap.
*/
public function getSqlDbMap()
{
return $this->sqldbmap;
}
/**
* Set the database name.
* @param string $database
*/
public function setDatabase($database)
{
$this->database = $database;
}
/**
* Get the database name.
* @return string
*/
public function getDatabase()
{
return $this->database;
}
/**
* Create the sql -> database map.
*
* @throws IOException - if unable to store properties
*/
protected function createSqlDbMap()
{
if ($this->getSqlDbMap() === null) {
return;
}
// Produce the sql -> database map
$sqldbmap = new Properties();
// Check to see if the sqldbmap has already been created.
if ($this->getSqlDbMap()->exists()) {
$sqldbmap->load($this->getSqlDbMap());
}
if ($this->packageObjectModel) {
// in this case we'll get the sql file name from the package attribute
$dataModels = $this->packageDataModels();
foreach ($dataModels as $package => $dataModel) {
foreach ($dataModel->getDatabases() as $database) {
$name = ($package ? $package . '.' : '') . 'schema.xml';
$sqlFile = $this->getMappedFile($name);
$sqldbmap->setProperty($sqlFile->getName(), $database->getName());
}
}
} else {
// the traditional way is to map the schema.xml filenames
$dmMap = $this->getDataModelDbMap();
foreach (array_keys($dmMap) as $dataModelName) {
$sqlFile = $this->getMappedFile($dataModelName);
if ($this->getDatabase() === null) {
$databaseName = $dmMap[$dataModelName];
} else {
$databaseName = $this->getDatabase();
}
$sqldbmap->setProperty($sqlFile->getName(), $databaseName);
}
}
try {
$sqldbmap->store($this->getSqlDbMap(), "Sqlfile -> Database map");
} catch (IOException $e) {
throw new IOException("Unable to store properties: ". $e->getMessage());
}
}
public function main() {
$this->validate();
if (!$this->mapperElement) {
throw new BuildException("You must use a <mapper/> element to describe how names should be transformed.");
}
if ($this->packageObjectModel) {
$dataModels = $this->packageDataModels();
} else {
$dataModels = $this->getDataModels();
}
// 1) first create a map of filenames to databases; this is used by other tasks like
// the SQLExec task.
$this->createSqlDbMap();
// 2) Now actually create the DDL based on the datamodel(s) from XML schema file.
$targetDatabase = $this->getTargetDatabase();
$generatorConfig = $this->getGeneratorConfig();
$builderClazz = $generatorConfig->getBuilderClassname('ddl');
foreach ($dataModels as $package => $dataModel) {
foreach ($dataModel->getDatabases() as $database) {
// Clear any start/end DLL
call_user_func(array($builderClazz, 'reset'));
// file we are going to create
if (!$this->packageObjectModel) {
$name = $dataModel->getName();
} else {
$name = ($package ? $package . '.' : '') . 'schema.xml';
}
$outFile = $this->getMappedFile($name);
$this->log("Writing to SQL file: " . $outFile->getPath());
// First add any "header" SQL
$ddl = call_user_func(array($builderClazz, 'getDatabaseStartDDL'));
foreach ($database->getTables() as $table) {
if (!$table->isSkipSql()) {
$builder = $generatorConfig->getConfiguredBuilder($table, 'ddl');
$this->log("\t+ " . $table->getName() . " [builder: " . get_class($builder) . "]");
$ddl .= $builder->build();
foreach ($builder->getWarnings() as $warning) {
$this->log($warning, Project::MSG_WARN);
}
} else {
$this->log("\t + (skipping) " . $table->getName());
}
} // foreach database->getTables()
// Finally check to see if there is any "footer" SQL
$ddl .= call_user_func(array($builderClazz, 'getDatabaseEndDDL'));
#var_dump($outFile->getAbsolutePath());
// Now we're done. Write the file!
file_put_contents($outFile->getAbsolutePath(), $ddl);
} // foreach database
} //foreach datamodels
} // main()
/**
* Packages the datamodels to one datamodel per package
*
* This applies only when the the packageObjectModel option is set. We need to
* re-package the datamodels to allow the database package attribute to control
* which tables go into which SQL file.
*
* @return array The packaged datamodels
*/
protected function packageDataModels() {
static $packagedDataModels;
if (is_null($packagedDataModels)) {
$dataModels = $this->getDataModels();
$dataModel = array_shift($dataModels);
$packagedDataModels = array();
$platform = $this->getGeneratorConfig()->getConfiguredPlatform();
foreach ($dataModel->getDatabases() as $db) {
foreach ($db->getTables() as $table) {
$package = $table->getPackage();
if (!isset($packagedDataModels[$package])) {
$dbClone = $this->cloneDatabase($db);
$dbClone->setPackage($package);
$ad = new AppData($platform);
$ad->setName($dataModel->getName());
$ad->addDatabase($dbClone);
$packagedDataModels[$package] = $ad;
}
$packagedDataModels[$package]->getDatabase($db->getName())->addTable($table);
}
}
}
return $packagedDataModels;
}
protected function cloneDatabase($db) {
$attributes = array (
'name' => $db->getName(),
'baseClass' => $db->getBaseClass(),
'basePeer' => $db->getBasePeer(),
'defaultIdMethod' => $db->getDefaultIdMethod(),
'defaultPhpNamingMethod' => $db->getDefaultPhpNamingMethod(),
'defaultTranslateMethod' => $db->getDefaultTranslateMethod(),
'heavyIndexing' => $db->getHeavyIndexing(),
);
$clone = new Database();
$clone->loadFromXML($attributes);
return $clone;
}
}

View file

@ -0,0 +1,548 @@
<?php
/**
* This file is part of the Propel package.
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @license MIT License
*/
require_once 'phing/tasks/ext/pdo/PDOTask.php';
require_once 'config/GeneratorConfig.php';
require_once 'model/PropelTypes.php';
/**
* This class generates an XML schema of an existing database from
* the database metadata.
*
* @author Hans Lellelid <hans@xmpl.org>
* @version $Revision: 1716 $
* @package propel.generator.task
*/
class PropelSchemaReverseTask extends PDOTask
{
/**
* Zero bit for no validators
*/
const VALIDATORS_NONE = 0;
/**
* Bit for maxLength validator
*/
const VALIDATORS_MAXLENGTH = 1;
/**
* Bit for maxValue validator
*/
const VALIDATORS_MAXVALUE = 2;
/**
* Bit for type validator
*/
const VALIDATORS_TYPE = 4;
/**
* Bit for required validator
*/
const VALIDATORS_REQUIRED = 8;
/**
* Bit for unique validator
*/
const VALIDATORS_UNIQUE = 16;
/**
* Bit for all validators
*/
const VALIDATORS_ALL = 255;
/**
* File to contain XML database schema.
* @var PhingFIle
*/
protected $xmlSchema;
/**
* DB encoding to use
* @var string
*/
protected $dbEncoding = 'iso-8859-1';
/**
* DB schema to use.
* @var string
*/
protected $dbSchema;
/**
* The datasource name (used for <database name=""> in schema.xml)
*
* @var string
*/
protected $databaseName;
/**
* DOM document produced.
* @var DOMDocument
*/
protected $doc;
/**
* The document root element.
* @var DOMElement
*/
protected $databaseNode;
/**
* Hashtable of columns that have primary keys.
* @var array
*/
protected $primaryKeys;
/**
* Whether to use same name for phpName or not.
* @var boolean
*/
protected $samePhpName;
/**
* whether to add vendor info or not
* @var boolean
*/
protected $addVendorInfo;
/**
* Bitfield to switch on/off which validators will be created.
*
* @var int
*/
protected $validatorBits = PropelSchemaReverseTask::VALIDATORS_NONE;
/**
* Collect validatorInfos to create validators.
*
* @var int
*/
protected $validatorInfos;
/**
* An initialized GeneratorConfig object containing the converted Phing props.
*
* @var GeneratorConfig
*/
private $generatorConfig;
/**
* Maps validator type tokens to bits
*
* The tokens are used in the propel.addValidators property to define
* which validators are to be added
*
* @var array
*/
static protected $validatorBitMap = array (
'none' => PropelSchemaReverseTask::VALIDATORS_NONE,
'maxlength' => PropelSchemaReverseTask::VALIDATORS_MAXLENGTH,
'maxvalue' => PropelSchemaReverseTask::VALIDATORS_MAXVALUE,
'type' => PropelSchemaReverseTask::VALIDATORS_TYPE,
'required' => PropelSchemaReverseTask::VALIDATORS_REQUIRED,
'unique' => PropelSchemaReverseTask::VALIDATORS_UNIQUE,
'all' => PropelSchemaReverseTask::VALIDATORS_ALL,
);
/**
* Defines messages that are added to validators
*
* @var array
*/
static protected $validatorMessages = array (
'maxlength' => array (
'msg' => 'The field %s must be not longer than %s characters.',
'var' => array('colName', 'value')
),
'maxvalue' => array (
'msg' => 'The field %s must be not greater than %s.',
'var' => array('colName', 'value')
),
'type' => array (
'msg' => 'The column %s must be an %s value.',
'var' => array('colName', 'value')
),
'required' => array (
'msg' => 'The field %s is required.',
'var' => array('colName')
),
'unique' => array (
'msg' => 'This %s already exists in table %s.',
'var' => array('colName', 'tableName')
),
);
/**
* Gets the (optional) schema name to use.
*
* @return string
*/
public function getDbSchema()
{
return $this->dbSchema;
}
/**
* Sets the name of a database schema to use (optional).
*
* @param string $dbSchema
*/
public function setDbSchema($dbSchema)
{
$this->dbSchema = $dbSchema;
}
/**
* Gets the database encoding.
*
* @return string
*/
public function getDbEncoding($v)
{
return $this->dbEncoding;
}
/**
* Sets the database encoding.
*
* @param string $v
*/
public function setDbEncoding($v)
{
$this->dbEncoding = $v;
}
/**
* Gets the datasource name.
*
* @return string
*/
public function getDatabaseName()
{
return $this->databaseName;
}
/**
* Sets the datasource name.
*
* This will be used as the <database name=""> value in the generated schema.xml
*
* @param string $v
*/
public function setDatabaseName($v)
{
$this->databaseName = $v;
}
/**
* Sets the output name for the XML file.
*
* @param PhingFile $v
*/
public function setOutputFile(PhingFile $v)
{
$this->xmlSchema = $v;
}
/**
* Set whether to use the column name as phpName without any translation.
*
* @param boolean $v
*/
public function setSamePhpName($v)
{
$this->samePhpName = $v;
}
/**
* Set whether to add vendor info to the schema.
*
* @param boolean $v
*/
public function setAddVendorInfo($v)
{
$this->addVendorInfo = (boolean) $v;
}
/**
* Sets set validator bitfield from a comma-separated list of "validator bit" names.
*
* @param string $v The comma-separated list of which validators to add.
* @return void
*/
public function setAddValidators($v)
{
$validKeys = array_keys(self::$validatorBitMap);
// lowercase input
$v = strtolower($v);
$bits = self::VALIDATORS_NONE;
$exprs = explode(',', $v);
foreach ($exprs as $expr) {
$expr = trim($expr);
if(!empty($expr)) {
if (!isset(self::$validatorBitMap[$expr])) {
throw new BuildException("Unable to interpret validator in expression ('$v'): " . $expr);
}
$bits |= self::$validatorBitMap[$expr];
}
}
$this->validatorBits = $bits;
}
/**
* Checks whether to add validators of specified type or not
*
* @param int $type The validator type constant.
* @return boolean
*/
protected function isValidatorRequired($type)
{
return (($this->validatorBits & $type) === $type);
}
/**
* Whether to use the column name as phpName without any translation.
*
* @return boolean
*/
public function isSamePhpName()
{
return $this->samePhpName;
}
/**
* @throws BuildException
*/
public function main()
{
if (!$this->getDatabaseName()) {
throw new BuildException("databaseName attribute is required for schema reverse engineering", $this->getLocation());
}
//(not yet supported) $this->log("schema : " . $this->dbSchema);
//DocumentTypeImpl docType = new DocumentTypeImpl(null, "database", null,
// "http://jakarta.apache.org/turbine/dtd/database.dtd");
$this->doc = new DOMDocument('1.0', 'utf-8');
$this->doc->formatOutput = true; // pretty printing
$this->doc->appendChild($this->doc->createComment("Autogenerated by ".get_class($this)." class."));
try {
$database = $this->buildModel();
if ($this->validatorBits !== self::VALIDATORS_NONE) {
$this->addValidators($database);
}
$database->appendXml($this->doc);
$this->log("Writing XML to file: " . $this->xmlSchema->getPath());
$out = new FileWriter($this->xmlSchema);
$xmlstr = $this->doc->saveXML();
$out->write($xmlstr);
$out->close();
} catch (Exception $e) {
$this->log("There was an error building XML from metadata: " . $e->getMessage(), Project::MSG_ERR);
}
$this->log("Schema reverse engineering finished");
}
/**
* Gets the GeneratorConfig object for this task or creates it on-demand.
* @return GeneratorConfig
*/
protected function getGeneratorConfig()
{
if ($this->generatorConfig === null) {
$this->generatorConfig = new GeneratorConfig();
$this->generatorConfig->setBuildProperties($this->getProject()->getProperties());
}
return $this->generatorConfig;
}
/**
* Builds the model classes from the database schema.
* @return Database The built-out Database (with all tables, etc.)
*/
protected function buildModel()
{
$config = $this->getGeneratorConfig();
$con = $this->getConnection();
$database = new Database($this->getDatabaseName());
$database->setPlatform($config->getConfiguredPlatform($con));
// Some defaults ...
$database->setDefaultIdMethod(IDMethod::NATIVE);
$parser = $config->getConfiguredSchemaParser($con);
$nbTables = $parser->parse($database, $this);
$this->log("Successfully Reverse Engineered " . $nbTables . " tables");
return $database;
}
/**
* Adds any requested validators to the data model.
*
* We will add the following type specific validators:
*
* for notNull columns: required validator
* for unique indexes: unique validator
* for varchar types: maxLength validators (CHAR, VARCHAR, LONGVARCHAR)
* for numeric types: maxValue validators (BIGINT, SMALLINT, TINYINT, INTEGER, FLOAT, DOUBLE, NUMERIC, DECIMAL, REAL)
* for integer and timestamp types: notMatch validator with [^\d]+ (BIGINT, SMALLINT, TINYINT, INTEGER, TIMESTAMP)
* for float types: notMatch validator with [^\d\.]+ (FLOAT, DOUBLE, NUMERIC, DECIMAL, REAL)
*
* @param Database $database The Database model.
* @return void
* @todo find out how to evaluate the appropriate size and adjust maxValue rule values appropriate
* @todo find out if float type column values must always notMatch('[^\d\.]+'), i.e. digits and point for any db vendor, language etc.
*/
protected function addValidators(Database $database)
{
$platform = $this->getGeneratorConfig()->getConfiguredPlatform();
foreach ($database->getTables() as $table) {
$set = new PropelSchemaReverse_ValidatorSet();
foreach ($table->getColumns() as $col) {
if ($col->isNotNull() && $this->isValidatorRequired(self::VALIDATORS_REQUIRED)) {
$validator = $set->getValidator($col);
$validator->addRule($this->getValidatorRule($col, 'required'));
}
if (in_array($col->getType(), array(PropelTypes::CHAR, PropelTypes::VARCHAR, PropelTypes::LONGVARCHAR))
&& $col->getSize() && $this->isValidatorRequired(self::VALIDATORS_MAXLENGTH)) {
$validator = $set->getValidator($col);
$validator->addRule($this->getValidatorRule($col, 'maxLength', $col->getSize()));
}
if ($col->isNumericType() && $this->isValidatorRequired(self::VALIDATORS_MAXVALUE)) {
$this->log("WARNING: maxValue validator added for column ".$col->getName().". You will have to adjust the size value manually.", Project::MSG_WARN);
$validator = $set->getValidator($col);
$validator->addRule($this->getValidatorRule($col, 'maxValue', 'REPLACEME'));
}
if ($col->isPhpPrimitiveType() && $this->isValidatorRequired(self::VALIDATORS_TYPE)) {
$validator = $set->getValidator($col);
$validator->addRule($this->getValidatorRule($col, 'type', $col->getPhpType()));
}
}
foreach ($table->getUnices() as $unique) {
$colnames = $unique->getColumns();
if (count($colnames) == 1) { // currently 'unique' validator only works w/ single columns.
$col = $table->getColumn($colnames[0]);
$validator = $set->getValidator($col);
$validator->addRule($this->getValidatorRule($col, 'unique'));
}
}
foreach ($set->getValidators() as $validator) {
$table->addValidator($validator);
}
} // foreach table
}
/**
* Gets validator rule for specified type (string).
*
* @param Column $column The column that is being validated.
* @param string $type The type (string) for validator (e.g. 'required').
* @param mixed $value The value for the validator (if applicable)
*/
protected function getValidatorRule(Column $column, $type, $value = null)
{
$rule = new Rule();
$rule->setName($type);
if ($value !== null) {
$rule->setValue($value);
}
$rule->setMessage($this->getRuleMessage($column, $type, $value));
return $rule;
}
/**
* Gets the message for a specified rule.
*
* @param Column $column
* @param string $type
* @param mixed $value
*/
protected function getRuleMessage(Column $column, $type, $value)
{
// create message
$colName = $column->getName();
$tableName = $column->getTable()->getName();
$msg = self::$validatorMessages[strtolower($type)];
$tmp = compact($msg['var']);
array_unshift($tmp, $msg['msg']);
$msg = call_user_func_array('sprintf', $tmp);
return $msg;
}
}
/**
* A helper class to store validator sets indexed by column.
* @package propel.generator.task
*/
class PropelSchemaReverse_ValidatorSet
{
/**
* Map of column names to validators.
*
* @var array Validator[]
*/
private $validators = array();
/**
* Gets a single validator for specified column name.
* @param Column $column
* @return Validator
*/
public function getValidator(Column $column)
{
$key = $column->getName();
if (!isset($this->validators[$key])) {
$this->validators[$key] = new Validator();
$this->validators[$key]->setColumn($column);
}
return $this->validators[$key];
}
/**
* Gets all validators.
* @return array Validator[]
*/
public function getValidators()
{
return $this->validators;
}
}