[ Index ] |
|
Code source de Symfony 1.0.0 |
1 <?php 2 3 /* 4 * $Id: AbstractPropelDataModelTask.php 337 2006-02-15 14:41:54Z hans $ 5 * 6 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 7 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 8 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 9 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 10 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 11 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 12 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 13 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 14 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 15 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 16 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 17 * 18 * This software consists of voluntary contributions made by many individuals 19 * and is licensed under the LGPL. For more information please see 20 * <http://propel.phpdb.org>. 21 */ 22 23 //include_once 'phing/tasks/ext/CapsuleTask.php'; 24 require_once 'phing/Task.php'; 25 include_once 'propel/engine/database/model/AppData.php'; 26 include_once 'propel/engine/database/model/Database.php'; 27 include_once 'propel/engine/database/transform/XmlToAppData.php'; 28 29 /** 30 * An abstract base Propel task to perform work related to the XML schema file. 31 * 32 * The subclasses invoke templates to do the actual writing of the resulting files. 33 * 34 * @author Hans Lellelid <hans@xmpl.org> (Propel) 35 * @author Jason van Zyl <jvanzyl@zenplex.com> (Torque) 36 * @author Daniel Rall <dlr@finemaltcoding.com> (Torque) 37 * @package propel.phing 38 */ 39 abstract class AbstractPropelDataModelTask extends Task { 40 41 /** 42 * Fileset of XML schemas which represent our data models. 43 * @var array Fileset[] 44 */ 45 protected $schemaFilesets = array(); 46 47 /** 48 * Data models that we collect. One from each XML schema file. 49 */ 50 protected $dataModels = array(); 51 52 /** 53 * Have datamodels been initialized? 54 * @var boolean 55 */ 56 private $dataModelsLoaded = false; 57 58 /** 59 * Map of data model name to database name. 60 * Should probably stick to the convention 61 * of them being the same but I know right now 62 * in a lot of cases they won't be. 63 */ 64 protected $dataModelDbMap; 65 66 /** 67 * Hashtable containing the names of all the databases 68 * in our collection of schemas. 69 */ 70 protected $databaseNames; // doesn't seem to be used anywhere 71 72 /** 73 * The target database(s) we are generating SQL 74 * for. Right now we can only deal with a single 75 * target, but we will support multiple targets 76 * soon. 77 */ 78 protected $targetDatabase; 79 80 /** 81 * DB encoding to use for XmlToAppData object 82 */ 83 protected $dbEncoding = 'iso-8859-1'; 84 85 /** 86 * Target PHP package to place the generated files in. 87 */ 88 protected $targetPackage; 89 90 /** 91 * @var Mapper 92 */ 93 protected $mapperElement; 94 95 /** 96 * Destination directory for results of template scripts. 97 * @var PhingFile 98 */ 99 protected $outputDirectory; 100 101 /** 102 * Path where Capsule looks for templates. 103 * @var PhingFile 104 */ 105 protected $templatePath; 106 107 /** 108 * Whether to package the datamodels or not 109 * @var PhingFile 110 */ 111 protected $packageObjectModel; 112 113 /** 114 * Whether to perform validation (XSD) on the schema.xml file(s). 115 * @var boolean 116 */ 117 protected $validate; 118 119 /** 120 * The XSD schema file to use for validation. 121 * @var PhingFile 122 */ 123 protected $xsdFile; 124 125 /** 126 * XSL file to use to normalize (or otherwise transform) schema before validation. 127 * @var PhingFile 128 */ 129 protected $xslFile; 130 131 /** 132 * Return the data models that have been 133 * processed. 134 * 135 * @return List data models 136 */ 137 public function getDataModels() 138 { 139 if (!$this->dataModelsLoaded) $this->loadDataModels(); 140 return $this->dataModels; 141 } 142 143 /** 144 * Return the data model to database name map. 145 * 146 * @return Hashtable data model name to database name map. 147 */ 148 public function getDataModelDbMap() 149 { 150 if (!$this->dataModelsLoaded) $this->loadDataModels(); 151 return $this->dataModelDbMap; 152 } 153 154 /** 155 * Adds a set of xml schema files (nested fileset attribute). 156 * 157 * @param set a Set of xml schema files 158 */ 159 public function addSchemaFileset(Fileset $set) 160 { 161 $this->schemaFilesets[] = $set; 162 } 163 164 /** 165 * Get the current target database. 166 * 167 * @return String target database(s) 168 */ 169 public function getTargetDatabase() 170 { 171 return $this->targetDatabase; 172 } 173 174 /** 175 * Set the current target database. (e.g. mysql, oracle, ..) 176 * 177 * @param v target database(s) 178 */ 179 public function setTargetDatabase($v) 180 { 181 $this->targetDatabase = $v; 182 } 183 184 /** 185 * Get the current target package. 186 * 187 * @return string target PHP package. 188 */ 189 public function getTargetPackage() 190 { 191 return $this->targetPackage; 192 } 193 194 /** 195 * Set the current target package. This is where generated PHP classes will 196 * live. 197 * 198 * @param string $v target PHP package. 199 */ 200 public function setTargetPackage($v) 201 { 202 $this->targetPackage = $v; 203 } 204 205 /** 206 * Set the packageObjectModel switch on/off 207 * 208 * @param string $v The build.property packageObjectModel 209 */ 210 public function setPackageObjectModel($v) 211 { 212 $this->packageObjectModel = ($v === '1' ? true : false); 213 } 214 215 /** 216 * Set whether to perform validation on the datamodel schema.xml file(s). 217 * @param boolean $v 218 */ 219 public function setValidate($v) 220 { 221 $this->validate = $v; 222 } 223 224 /** 225 * Set the XSD schema to use for validation of any datamodel schema.xml file(s). 226 * @param $v PhingFile 227 */ 228 public function setXsd(PhingFile $v) 229 { 230 $this->xsdFile = $v; 231 } 232 233 /** 234 * Set the normalization XSLT to use to transform datamodel schema.xml file(s) before validation and parsing. 235 * @param $v PhingFile 236 */ 237 public function setXsl(PhingFile $v) 238 { 239 $this->xslFile = $v; 240 } 241 242 /** 243 * [REQUIRED] Set the path where Capsule will look 244 * for templates using the file template 245 * loader. 246 * @return void 247 * @throws Exception 248 */ 249 public function setTemplatePath($templatePath) { 250 $resolvedPath = ""; 251 $tok = strtok($templatePath, ","); 252 while ( $tok ) { 253 // resolve relative path from basedir and leave 254 // absolute path untouched. 255 $fullPath = $this->project->resolveFile($tok); 256 $cpath = $fullPath->getCanonicalPath(); 257 if ($cpath === false) { 258 $this->log("Template directory does not exist: " . $fullPath->getAbsolutePath()); 259 } else { 260 $resolvedPath .= $cpath; 261 } 262 $tok = strtok(","); 263 if ( $tok ) { 264 $resolvedPath .= ","; 265 } 266 } 267 $this->templatePath = $resolvedPath; 268 } 269 270 /** 271 * Get the path where Velocity will look 272 * for templates using the file template 273 * loader. 274 * @return string 275 */ 276 public function getTemplatePath() { 277 return $this->templatePath; 278 } 279 280 /** 281 * [REQUIRED] Set the output directory. It will be 282 * created if it doesn't exist. 283 * @param PhingFile $outputDirectory 284 * @return void 285 * @throws Exception 286 */ 287 public function setOutputDirectory(PhingFile $outputDirectory) { 288 try { 289 if (!$outputDirectory->exists()) { 290 $this->log("Output directory does not exist, creating: " . $outputDirectory->getPath(),PROJECT_MSG_VERBOSE); 291 if (!$outputDirectory->mkdirs()) { 292 throw new IOException("Unable to create Ouptut directory: " . $outputDirectory->getAbsolutePath()); 293 } 294 } 295 $this->outputDirectory = $outputDirectory->getCanonicalPath(); 296 } catch (IOException $ioe) { 297 throw new BuildException($ioe); 298 } 299 } 300 301 /** 302 * Set the current target database encoding. 303 * 304 * @param v target database encoding 305 */ 306 public function setDbEncoding($v) 307 { 308 $this->dbEncoding = $v; 309 } 310 311 /** 312 * Get the output directory. 313 * @return string 314 */ 315 public function getOutputDirectory() { 316 return $this->outputDirectory; 317 } 318 319 /** 320 * Nested creator, creates one Mapper for this task. 321 * 322 * @return Mapper The created Mapper type object. 323 * @throws BuildException 324 */ 325 public function createMapper() { 326 if ($this->mapperElement !== null) { 327 throw new BuildException("Cannot define more than one mapper.", $this->location); 328 } 329 $this->mapperElement = new Mapper($this->project); 330 return $this->mapperElement; 331 } 332 333 /** 334 * Maps the passed in name to a new filename & returns resolved File object. 335 * @param string $from 336 * @return PhingFile Resolved File object. 337 * @throws BuilException - if no Mapper element se 338 * - if unable to map new filename. 339 */ 340 protected function getMappedFile($from) 341 { 342 if(!$this->mapperElement) { 343 throw new BuildException("This task requires you to use a <mapper/> element to describe how filename changes should be handled."); 344 } 345 346 $mapper = $this->mapperElement->getImplementation(); 347 $mapped = $mapper->main($from); 348 if (!$mapped) { 349 throw new BuildException("Cannot create new filename based on: " . $from); 350 } 351 // Mappers always return arrays since it's possible for some mappers to map to multiple names. 352 $outFilename = array_shift($mapped); 353 $outFile = new PhingFile($this->getOutputDirectory(), $outFilename); 354 return $outFile; 355 } 356 357 /** 358 * Get the Platform class based on the target database type. 359 * @return Platform Class that implements the Platform interface. 360 */ 361 protected function getPlatformForTargetDatabase() 362 { 363 364 $classpath = $this->getPropelProperty("platformClass"); 365 if (empty($classpath)) { 366 throw new BuildException("Unable to find class path for '$propname' property."); 367 } 368 369 // This is a slight hack to workaround camel case inconsistencies for the DDL classes. 370 // Basically, we want to turn ?.?.?.sqliteDDLBuilder into ?.?.?.SqliteDDLBuilder 371 $lastdotpos = strrpos($classpath, '.'); 372 if ($lastdotpos) $classpath{$lastdotpos+1} = strtoupper($classpath{$lastdotpos+1}); 373 else ucfirst($classpath); 374 375 if (empty($classpath)) { 376 throw new BuildException("Unable to find class path for '$propname' property."); 377 } 378 379 $clazz = Phing::import($classpath); 380 return new $clazz(); 381 } 382 383 /** 384 * Gets all matching XML schema files and loads them into data models for class. 385 * @return void 386 */ 387 protected function loadDataModels() 388 { 389 $ads = array(); 390 391 // Get all matched files from schemaFilesets 392 foreach($this->schemaFilesets as $fs) { 393 $ds = $fs->getDirectoryScanner($this->project); 394 $srcDir = $fs->getDir($this->project); 395 396 $dataModelFiles = $ds->getIncludedFiles(); 397 398 $platform = $this->getPlatformForTargetDatabase(); 399 400 // Make a transaction for each file 401 foreach($dataModelFiles as $dmFilename) { 402 403 $this->log("Processing: ".$dmFilename); 404 $xmlFile = new PhingFile($srcDir, $dmFilename); 405 406 $dom = new DomDocument('1.0', 'UTF-8'); 407 $dom->load($xmlFile->getAbsolutePath()); 408 409 // normalize (or transform) the XML document using XSLT 410 if ($this->xslFile) { 411 $this->log("Transforming " . $xmlFile->getPath() . " using stylesheet " . $this->xslFile->getPath(), PROJECT_MSG_VERBOSE); 412 if (!class_exists('XSLTProcessor')) { 413 $this->log("Could not perform XLST transformation. Make sure PHP has been compiled/configured to support XSLT.", PROJECT_MSG_ERR); 414 } else { 415 // normalize the document using normalizer stylesheet 416 417 $xsl = new XsltProcessor(); 418 $xsl->importStyleSheet(DomDocument::load($this->xslFile->getAbsolutePath())); 419 $transformed = $xsl->transformToDoc($dom); 420 $newXmlFilename = substr($xmlFile->getName(), 0, strrpos($xmlFile->getName(), '.')) . '-transformed.xml'; 421 422 // now overwrite previous vars to point to newly transformed file 423 $xmlFile = new PhingFile($srcDir, $newXmlFilename); 424 $transformed->save($xmlFile->getAbsolutePath()); 425 $this->log("\t- Using new (post-transformation) XML file: " . $xmlFile->getPath(), PROJECT_MSG_VERBOSE); 426 427 $dom = new DomDocument('1.0', 'UTF-8'); 428 $dom->load($xmlFile->getAbsolutePath()); 429 } 430 } 431 432 // validate the XML document using XSD schema 433 if ($this->validate && $this->xsdFile) { 434 $this->log("Validating XML doc (".$xmlFile->getPath().") using schema file " . $this->xsdFile->getPath(), PROJECT_MSG_VERBOSE); 435 if (!$dom->schemaValidate($this->xsdFile->getAbsolutePath())) { 436 throw new BuildException("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)."); throw new EngineException("XML schema does not validate (using schema file $xsdFile). 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()); 437 } 438 } 439 440 $xmlParser = new XmlToAppData($platform, $this->getTargetPackage(), $this->dbEncoding); 441 $ad = $xmlParser->parseFile($xmlFile->getAbsolutePath()); 442 $ad->setName($dmFilename); // <-- Important: use the original name, not the -transformed name. 443 $ads[] = $ad; 444 } 445 } 446 447 if (empty($ads)) { 448 throw new BuildException("No schema files were found (matching your schema fileset definition)."); 449 } 450 451 if (!$this->packageObjectModel) { 452 453 $this->dataModels = $ads; 454 $this->databaseNames = array(); // doesn't seem to be used anywhere 455 $this->dataModelDbMap = array(); 456 457 // Different datamodels may state the same database 458 // names, we just want the unique names of databases. 459 foreach($this->dataModels as $dm) { 460 $database = $dm->getDatabase(); 461 $this->dataModelDbMap[$dm->getName()] = $database->getName(); 462 $this->databaseNames[$database->getName()] = $database->getName(); // making list of *unique* dbnames. 463 } 464 } else { 465 466 $this->joinDatamodels($ads); 467 $this->dataModels[0]->getDatabases(); // calls doFinalInitialization() 468 } 469 470 $this->dataModelsLoaded = true; 471 } 472 473 /** 474 * Joins the datamodels collected from schema.xml files into one big datamodel 475 * 476 * This applies only when the the packageObjectModel option is set. We need to 477 * join the datamodels in this case to allow for foreign keys that point to 478 * tables in different packages. 479 * 480 * @param array $ads The datamodels to join 481 */ 482 protected function joinDatamodels($ads) { 483 484 foreach($ads as $ad) { 485 $db = $ad->getDatabase(null, false); 486 $this->dataModelDbMap[$ad->getName()] = $db->getName(); 487 } 488 489 foreach ($ads as $addAd) { 490 491 $ad = &$this->dataModels[0]; 492 if (!isset($ad)) { 493 $addAd->setName('JoinedDataModel'); 494 $ad = $addAd; 495 continue; 496 } 497 foreach ($addAd->getDatabases(false) as $addDb) { 498 $addDbName = $addDb->getName(); 499 if (!$package = $addDb->getPackage()) { 500 throw new BuildException('No package found for database "' . $addDbName . '" in ' . $addAd->getName() . '. The propel.packageObjectModel property requires the package attribute to be set for each database.'); 501 } 502 $db = $ad->getDatabase($addDbName, false); 503 if (!$db) { 504 $ad->addDatabase($addDb); 505 continue; 506 } 507 foreach ($addDb->getTables() as $addTable) { 508 $table = $db->getTable($addTable->getName()); 509 if ($table) { 510 throw new BuildException('Duplicate table found: ' . $addDbName . '.'); 511 } 512 $db->addTable($addTable); 513 } 514 } 515 } 516 } 517 518 /** 519 * Creates a new Capsule context with some basic properties set. 520 * (Capsule is a simple PHP encapsulation system -- aka a php "template" class.) 521 * @return Capsule 522 */ 523 protected function createContext() { 524 525 $context = new Capsule(); 526 527 // Make sure the output directory exists, if it doesn't 528 // then create it. 529 $outputDir = new PhingFile($this->outputDirectory); 530 if (!$outputDir->exists()) { 531 $this->log("Output directory does not exist, creating: " . $outputDir->getAbsolutePath()); 532 $outputDir->mkdirs(); 533 } 534 535 // Place our set of data models into the context along 536 // with the names of the databases as a convenience for now. 537 $context->put("targetDatabase", $this->targetDatabase); 538 $context->put("targetPackage", $this->targetPackage); 539 $context->put("now", strftime("%c")); 540 541 $this->log("Target database type: " . $this->targetDatabase); 542 $this->log("Target package: " . $this->targetPackage); 543 $this->log("Using template path: " . $this->templatePath); 544 $this->log("Output directory: " . $this->outputDirectory); 545 546 $context->setTemplatePath($this->templatePath); 547 $context->setOutputDirectory($this->outputDirectory); 548 549 $this->populateContextProperties($context); 550 551 return $context; 552 } 553 554 /** 555 * Fetches the propel.xxx properties from project, renaming the propel.xxx properties to just xxx. 556 * 557 * Also, renames any xxx.yyy properties to xxxYyy as PHP doesn't like the xxx.yyy syntax. 558 * 559 * @return array Assoc array of properties. 560 */ 561 protected function getPropelProperties() 562 { 563 $allProps = $this->getProject()->getProperties(); 564 $renamedPropelProps = array(); 565 foreach ($allProps as $key => $propValue) { 566 if (strpos($key, "propel.") === 0) { 567 $newKey = substr($key, strlen("propel.")); 568 $j = strpos($newKey, '.'); 569 while ($j !== false) { 570 $newKey = substr($newKey, 0, $j) . ucfirst(substr($newKey, $j + 1)); 571 $j = strpos($newKey, '.'); 572 } 573 $renamedPropelProps[$newKey] = $propValue; 574 } 575 } 576 return $renamedPropelProps; 577 } 578 579 /** 580 * Fetches a single propel.xxx property from project, using "converted" property names. 581 * @see getPropelProperties() 582 * @param string $name Name of property to fetch (in converted CamelCase) 583 * @return string The value of the property (or NULL if not set) 584 */ 585 protected function getPropelProperty($name) 586 { 587 $props = $this->getPropelProperties(); 588 if (isset($props[$name])) { 589 return $props[$name]; 590 } 591 return null; // just to be explicit 592 } 593 594 /** 595 * Adds the propel.xxx properties to the passed Capsule context, changing names to just xxx. 596 * 597 * Also, move xxx.yyy properties to xxxYyy as PHP doesn't like the xxx.yyy syntax. 598 * 599 * @param Capsule $context 600 * @see getPropelProperties() 601 */ 602 public function populateContextProperties(Capsule $context) 603 { 604 foreach ($this->getPropelProperties() as $key => $propValue) { 605 $this->log('Adding property ${' . $key . '} to context', PROJECT_MSG_DEBUG); 606 $context->put($key, $propValue); 607 } 608 } 609 610 /** 611 * Checks this class against Basic requrements of any propel datamodel task. 612 * 613 * @throws BuildException - if schema fileset was not defined 614 * - if no output directory was specified 615 */ 616 protected function validate() 617 { 618 if (empty($this->schemaFilesets)) { 619 throw new BuildException("You must specify a fileset of XML schemas.", $this->getLocation()); 620 } 621 622 // Make sure the output directory is set. 623 if ($this->outputDirectory === null) { 624 throw new BuildException("The output directory needs to be defined!", $this->getLocation()); 625 } 626 627 if ($this->validate) { 628 if (!$this->xsdFile) { 629 throw new BuildException("'validate' set to TRUE, but no XSD specified (use 'xsd' attribute).", $this->getLocation()); 630 } 631 } 632 633 } 634 635 }
titre
Description
Corps
titre
Description
Corps
titre
Description
Corps
titre
Corps
Généré le : Fri Mar 16 22:42:14 2007 | par Balluche grâce à PHPXref 0.7 |