PageRenderTime 189ms CodeModel.GetById 31ms RepoModel.GetById 12ms app.codeStats 0ms

/generator/lib/task/AbstractPropelDataModelTask.php

https://github.com/1989gaurav/Propel
PHP | 573 lines | 315 code | 53 blank | 205 comment | 20 complexity | b2a71c834a5511f9fd298e0b5f625ede MD5 | raw file
  1. <?php
  2. /**
  3. * This file is part of the Propel package.
  4. * For the full copyright and license information, please view the LICENSE
  5. * file that was distributed with this source code.
  6. *
  7. * @license MIT License
  8. */
  9. //include_once 'phing/tasks/ext/CapsuleTask.php';
  10. require_once 'phing/Task.php';
  11. include_once 'config/GeneratorConfig.php';
  12. include_once 'model/AppData.php';
  13. include_once 'model/Database.php';
  14. include_once 'builder/util/XmlToAppData.php';
  15. /**
  16. * An abstract base Propel task to perform work related to the XML schema file.
  17. *
  18. * The subclasses invoke templates to do the actual writing of the resulting files.
  19. *
  20. * @author Hans Lellelid <hans@xmpl.org> (Propel)
  21. * @author Jason van Zyl <jvanzyl@zenplex.com> (Torque)
  22. * @author Daniel Rall <dlr@finemaltcoding.com> (Torque)
  23. * @package propel.generator.task
  24. */
  25. abstract class AbstractPropelDataModelTask extends Task
  26. {
  27. /**
  28. * Fileset of XML schemas which represent our data models.
  29. * @var array Fileset[]
  30. */
  31. protected $schemaFilesets = array();
  32. /**
  33. * Data models that we collect. One from each XML schema file.
  34. */
  35. protected $dataModels = array();
  36. /**
  37. * Have datamodels been initialized?
  38. * @var boolean
  39. */
  40. private $dataModelsLoaded = false;
  41. /**
  42. * Map of data model name to database name.
  43. * Should probably stick to the convention
  44. * of them being the same but I know right now
  45. * in a lot of cases they won't be.
  46. */
  47. protected $dataModelDbMap;
  48. /**
  49. * The target database(s) we are generating SQL
  50. * for. Right now we can only deal with a single
  51. * target, but we will support multiple targets
  52. * soon.
  53. */
  54. protected $targetDatabase;
  55. /**
  56. * DB encoding to use for XmlToAppData object
  57. */
  58. protected $dbEncoding = 'iso-8859-1';
  59. /**
  60. * Target PHP package to place the generated files in.
  61. */
  62. protected $targetPackage;
  63. /**
  64. * @var Mapper
  65. */
  66. protected $mapperElement;
  67. /**
  68. * Destination directory for results of template scripts.
  69. * @var PhingFile
  70. */
  71. protected $outputDirectory;
  72. /**
  73. * Whether to package the datamodels or not
  74. * @var PhingFile
  75. */
  76. protected $packageObjectModel;
  77. /**
  78. * Whether to perform validation (XSD) on the schema.xml file(s).
  79. * @var boolean
  80. */
  81. protected $validate;
  82. /**
  83. * The XSD schema file to use for validation.
  84. * @var PhingFile
  85. */
  86. protected $xsdFile;
  87. /**
  88. * XSL file to use to normalize (or otherwise transform) schema before validation.
  89. * @var PhingFile
  90. */
  91. protected $xslFile;
  92. /**
  93. * Optional database connection url.
  94. * @var string
  95. */
  96. private $url = null;
  97. /**
  98. * Optional database connection user name.
  99. * @var string
  100. */
  101. private $userId = null;
  102. /**
  103. * Optional database connection password.
  104. * @var string
  105. */
  106. private $password = null;
  107. /**
  108. * PDO Connection.
  109. * @var PDO
  110. */
  111. private $conn = false;
  112. /**
  113. * An initialized GeneratorConfig object containing the converted Phing props.
  114. *
  115. * @var GeneratorConfig
  116. */
  117. private $generatorConfig;
  118. /**
  119. * Return the data models that have been
  120. * processed.
  121. *
  122. * @return List data models
  123. */
  124. public function getDataModels()
  125. {
  126. if (!$this->dataModelsLoaded) {
  127. $this->loadDataModels();
  128. }
  129. return $this->dataModels;
  130. }
  131. /**
  132. * Return the data model to database name map.
  133. *
  134. * @return Hashtable data model name to database name map.
  135. */
  136. public function getDataModelDbMap()
  137. {
  138. if (!$this->dataModelsLoaded) {
  139. $this->loadDataModels();
  140. }
  141. return $this->dataModelDbMap;
  142. }
  143. /**
  144. * Adds a set of xml schema files (nested fileset attribute).
  145. *
  146. * @param set a Set of xml schema files
  147. */
  148. public function addSchemaFileset(Fileset $set)
  149. {
  150. $this->schemaFilesets[] = $set;
  151. }
  152. /**
  153. * Get the current target database.
  154. *
  155. * @return String target database(s)
  156. */
  157. public function getTargetDatabase()
  158. {
  159. return $this->targetDatabase;
  160. }
  161. /**
  162. * Set the current target database. (e.g. mysql, oracle, ..)
  163. *
  164. * @param v target database(s)
  165. */
  166. public function setTargetDatabase($v)
  167. {
  168. $this->targetDatabase = $v;
  169. }
  170. /**
  171. * Get the current target package.
  172. *
  173. * @return string target PHP package.
  174. */
  175. public function getTargetPackage()
  176. {
  177. return $this->targetPackage;
  178. }
  179. /**
  180. * Set the current target package. This is where generated PHP classes will
  181. * live.
  182. *
  183. * @param string $v target PHP package.
  184. */
  185. public function setTargetPackage($v)
  186. {
  187. $this->targetPackage = $v;
  188. }
  189. /**
  190. * Set the packageObjectModel switch on/off
  191. *
  192. * @param boolean $v The build.property packageObjectModel
  193. */
  194. public function setPackageObjectModel($v)
  195. {
  196. $this->packageObjectModel = (boolean) $v;
  197. }
  198. /**
  199. * Set whether to perform validation on the datamodel schema.xml file(s).
  200. * @param boolean $v
  201. */
  202. public function setValidate($v)
  203. {
  204. $this->validate = (boolean) $v;
  205. }
  206. /**
  207. * Set the XSD schema to use for validation of any datamodel schema.xml file(s).
  208. * @param $v PhingFile
  209. */
  210. public function setXsd(PhingFile $v)
  211. {
  212. $this->xsdFile = $v;
  213. }
  214. /**
  215. * Set the normalization XSLT to use to transform datamodel schema.xml file(s) before validation and parsing.
  216. * @param $v PhingFile
  217. */
  218. public function setXsl(PhingFile $v)
  219. {
  220. $this->xslFile = $v;
  221. }
  222. /**
  223. * [REQUIRED] Set the output directory. It will be
  224. * created if it doesn't exist.
  225. * @param PhingFile $outputDirectory
  226. * @return void
  227. * @throws Exception
  228. */
  229. public function setOutputDirectory(PhingFile $outputDirectory) {
  230. try {
  231. if (!$outputDirectory->exists()) {
  232. $this->log("Output directory does not exist, creating: " . $outputDirectory->getPath(),Project::MSG_VERBOSE);
  233. if (!$outputDirectory->mkdirs()) {
  234. throw new IOException("Unable to create Ouptut directory: " . $outputDirectory->getAbsolutePath());
  235. }
  236. }
  237. $this->outputDirectory = $outputDirectory->getCanonicalPath();
  238. } catch (IOException $ioe) {
  239. throw new BuildException($ioe);
  240. }
  241. }
  242. /**
  243. * Set the current target database encoding.
  244. *
  245. * @param v target database encoding
  246. */
  247. public function setDbEncoding($v)
  248. {
  249. $this->dbEncoding = $v;
  250. }
  251. /**
  252. * Set the DB connection url.
  253. *
  254. * @param string $url connection url
  255. */
  256. public function setUrl($url)
  257. {
  258. $this->url = $url;
  259. }
  260. /**
  261. * Set the user name for the DB connection.
  262. *
  263. * @param string $userId database user
  264. */
  265. public function setUserid($userId)
  266. {
  267. $this->userId = $userId;
  268. }
  269. /**
  270. * Set the password for the DB connection.
  271. *
  272. * @param string $password database password
  273. */
  274. public function setPassword($password)
  275. {
  276. $this->password = $password;
  277. }
  278. /**
  279. * Get the output directory.
  280. * @return string
  281. */
  282. public function getOutputDirectory() {
  283. return $this->outputDirectory;
  284. }
  285. /**
  286. * Nested creator, creates one Mapper for this task.
  287. *
  288. * @return Mapper The created Mapper type object.
  289. * @throws BuildException
  290. */
  291. public function createMapper() {
  292. if ($this->mapperElement !== null) {
  293. throw new BuildException("Cannot define more than one mapper.", $this->location);
  294. }
  295. $this->mapperElement = new Mapper($this->project);
  296. return $this->mapperElement;
  297. }
  298. /**
  299. * Maps the passed in name to a new filename & returns resolved File object.
  300. * @param string $from
  301. * @return PhingFile Resolved File object.
  302. * @throws BuilException - if no Mapper element se
  303. * - if unable to map new filename.
  304. */
  305. protected function getMappedFile($from)
  306. {
  307. if (!$this->mapperElement) {
  308. throw new BuildException("This task requires you to use a <mapper/> element to describe how filename changes should be handled.");
  309. }
  310. $mapper = $this->mapperElement->getImplementation();
  311. $mapped = $mapper->main($from);
  312. if (!$mapped) {
  313. throw new BuildException("Cannot create new filename based on: " . $from);
  314. }
  315. // Mappers always return arrays since it's possible for some mappers to map to multiple names.
  316. $outFilename = array_shift($mapped);
  317. $outFile = new PhingFile($this->getOutputDirectory(), $outFilename);
  318. return $outFile;
  319. }
  320. /**
  321. * Gets the PDO connection, if URL specified.
  322. * @return PDO Connection to use (for quoting, Platform class, etc.) or NULL if no connection params were specified.
  323. */
  324. public function getConnection()
  325. {
  326. if ($this->conn === false) {
  327. $this->conn = null;
  328. if ($this->url) {
  329. $buf = "Using database settings:\n"
  330. . " URL: " . $this->url . "\n"
  331. . ($this->userId ? " user: " . $this->userId . "\n" : "")
  332. . ($this->password ? " password: " . $this->password . "\n" : "");
  333. $this->log($buf, Project::MSG_VERBOSE);
  334. // Set user + password to null if they are empty strings
  335. if (!$this->userId) { $this->userId = null; }
  336. if (!$this->password) { $this->password = null; }
  337. try {
  338. $this->conn = new PDO($this->url, $this->userId, $this->password);
  339. $this->conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
  340. } catch (PDOException $x) {
  341. $this->log("Unable to create a PDO connection: " . $x->getMessage(), Project::MSG_WARN);
  342. }
  343. }
  344. }
  345. return $this->conn;
  346. }
  347. /**
  348. * Gets all matching XML schema files and loads them into data models for class.
  349. * @return void
  350. */
  351. protected function loadDataModels()
  352. {
  353. $ads = array();
  354. $totalNbTables = 0;
  355. $this->log('Loading XML schema files...');
  356. // Get all matched files from schemaFilesets
  357. foreach ($this->schemaFilesets as $fs) {
  358. $ds = $fs->getDirectoryScanner($this->project);
  359. $srcDir = $fs->getDir($this->project);
  360. $dataModelFiles = $ds->getIncludedFiles();
  361. sort($dataModelFiles);
  362. $defaultPlatform = $this->getGeneratorConfig()->getConfiguredPlatform();
  363. // Make a transaction for each file
  364. foreach ($dataModelFiles as $dmFilename) {
  365. $this->log("Processing: ".$dmFilename, Project::MSG_VERBOSE);
  366. $xmlFile = new PhingFile($srcDir, $dmFilename);
  367. $dom = new DomDocument('1.0', 'UTF-8');
  368. $dom->load($xmlFile->getAbsolutePath());
  369. // modify schema to include any external schemas (and remove the external-schema nodes)
  370. $this->includeExternalSchemas($dom, $srcDir);
  371. // normalize (or transform) the XML document using XSLT
  372. if ($this->getGeneratorConfig()->getBuildProperty('schemaTransform') && $this->xslFile) {
  373. $this->log("Transforming " . $dmFilename . " using stylesheet " . $this->xslFile->getPath(), Project::MSG_VERBOSE);
  374. if (!class_exists('XSLTProcessor')) {
  375. $this->log("Could not perform XLST transformation. Make sure PHP has been compiled/configured to support XSLT.", Project::MSG_ERR);
  376. } else {
  377. // normalize the document using normalizer stylesheet
  378. $xslDom = new DomDocument('1.0', 'UTF-8');
  379. $xslDom->load($this->xslFile->getAbsolutePath());
  380. $xsl = new XsltProcessor();
  381. $xsl->importStyleSheet($xslDom);
  382. $dom = $xsl->transformToDoc($dom);
  383. }
  384. }
  385. // validate the XML document using XSD schema
  386. if ($this->validate && $this->xsdFile) {
  387. $this->log(" Validating XML using schema " . $this->xsdFile->getPath(), Project::MSG_VERBOSE);
  388. if (!$dom->schemaValidate($this->xsdFile->getAbsolutePath())) {
  389. 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());
  390. }
  391. }
  392. $xmlParser = new XmlToAppData($defaultPlatform, $this->getTargetPackage(), $this->dbEncoding);
  393. $xmlParser->setGeneratorConfig($this->getGeneratorConfig());
  394. $ad = $xmlParser->parseString($dom->saveXML(), $xmlFile->getAbsolutePath());
  395. $nbTables = $ad->getDatabase(null, false)->countTables();
  396. $totalNbTables += $nbTables;
  397. $this->log(sprintf(' %d tables processed successfully', $nbTables), Project::MSG_VERBOSE);
  398. $ad->setName($dmFilename);
  399. $ads[] = $ad;
  400. }
  401. $this->log(sprintf('%d tables found in %d schema files.', $totalNbTables, count($dataModelFiles)));
  402. }
  403. if (empty($ads)) {
  404. throw new BuildException("No schema files were found (matching your schema fileset definition).");
  405. }
  406. foreach ($ads as $ad) {
  407. // map schema filename with database name
  408. $this->dataModelDbMap[$ad->getName()] = $ad->getDatabase(null, false)->getName();
  409. }
  410. if (count($ads)>1 && $this->packageObjectModel) {
  411. $ad = $this->joinDataModels($ads);
  412. $this->dataModels = array($ad);
  413. } else {
  414. $this->dataModels = $ads;
  415. }
  416. foreach ($this->dataModels as &$ad) {
  417. $ad->doFinalInitialization();
  418. }
  419. $this->dataModelsLoaded = true;
  420. }
  421. /**
  422. * Replaces all external-schema nodes with the content of xml schema that node refers to
  423. *
  424. * Recurses to include any external schema referenced from in an included xml (and deeper)
  425. * Note: this function very much assumes at least a reasonable XML schema, maybe it'll proof
  426. * users don't have those and adding some more informative exceptions would be better
  427. *
  428. * @param DomDocument $dom
  429. * @param string $srcDir
  430. * @return void (objects, DomDocument, are references by default in PHP 5, so returning it is useless)
  431. **/
  432. protected function includeExternalSchemas(DomDocument $dom, $srcDir) {
  433. $databaseNode = $dom->getElementsByTagName("database")->item(0);
  434. $externalSchemaNodes = $dom->getElementsByTagName("external-schema");
  435. $fs = FileSystem::getFileSystem();
  436. $nbIncludedSchemas = 0;
  437. while ($externalSchema = $externalSchemaNodes->item(0)) {
  438. $include = $externalSchema->getAttribute("filename");
  439. $this->log('Processing external schema: ' . $include, Project::MSG_VERBOSE);
  440. $externalSchema->parentNode->removeChild($externalSchema);
  441. if ($fs->prefixLength($include) != 0) {
  442. $externalSchemaFile = new PhingFile($include);
  443. } else {
  444. $externalSchemaFile = new PhingFile($srcDir, $include);
  445. }
  446. $externalSchemaDom = new DomDocument('1.0', 'UTF-8');
  447. $externalSchemaDom->load($externalSchemaFile->getAbsolutePath());
  448. // The external schema may have external schemas of its own ; recurse
  449. $this->includeExternalSchemas($externalSchemaDom, $srcDir);
  450. 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
  451. $databaseNode->appendChild($dom->importNode($tableNode, true));
  452. }
  453. $nbIncludedSchemas++;
  454. }
  455. return $nbIncludedSchemas;
  456. }
  457. /**
  458. * Joins the datamodels collected from schema.xml files into one big datamodel.
  459. * We need to join the datamodels in this case to allow for foreign keys
  460. * that point to tables in different packages.
  461. *
  462. * @param array[AppData] $ads The datamodels to join
  463. * @return AppData The single datamodel with all other datamodels joined in
  464. */
  465. protected function joinDataModels($ads)
  466. {
  467. $mainAppData = array_shift($ads);
  468. $mainAppData->joinAppDatas($ads);
  469. return $mainAppData;
  470. }
  471. /**
  472. * Gets the GeneratorConfig object for this task or creates it on-demand.
  473. * @return GeneratorConfig
  474. */
  475. protected function getGeneratorConfig()
  476. {
  477. if ($this->generatorConfig === null) {
  478. $this->generatorConfig = new GeneratorConfig();
  479. $this->generatorConfig->setBuildProperties($this->getProject()->getProperties());
  480. }
  481. return $this->generatorConfig;
  482. }
  483. /**
  484. * Checks this class against Basic requrements of any propel datamodel task.
  485. *
  486. * @throws BuildException - if schema fileset was not defined
  487. * - if no output directory was specified
  488. */
  489. protected function validate()
  490. {
  491. if (empty($this->schemaFilesets)) {
  492. throw new BuildException("You must specify a fileset of XML schemas.", $this->getLocation());
  493. }
  494. // Make sure the output directory is set.
  495. if ($this->outputDirectory === null) {
  496. throw new BuildException("The output directory needs to be defined!", $this->getLocation());
  497. }
  498. if ($this->validate) {
  499. if (!$this->xsdFile) {
  500. throw new BuildException("'validate' set to TRUE, but no XSD specified (use 'xsd' attribute).", $this->getLocation());
  501. }
  502. }
  503. }
  504. }