PageRenderTime 25ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 0ms

/src/Propel/Generator/Manager/AbstractManager.php

http://github.com/propelorm/Propel2
PHP | 526 lines | 317 code | 42 blank | 167 comment | 15 complexity | 6d34b3663496ce3ec70dfbca4b2126c3 MD5 | raw file
  1. <?php
  2. /**
  3. * MIT License. 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. namespace Propel\Generator\Manager;
  8. use Closure;
  9. use DOMDocument;
  10. use Exception;
  11. use Propel\Generator\Builder\Util\SchemaReader;
  12. use Propel\Generator\Config\GeneratorConfigInterface;
  13. use Propel\Generator\Exception\BuildException;
  14. use Propel\Generator\Exception\EngineException;
  15. use XsltProcessor;
  16. /**
  17. * An abstract base Propel manager to perform work related to the XML schema
  18. * file.
  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. */
  24. abstract class AbstractManager
  25. {
  26. /**
  27. * @var array
  28. * Data models that we collect. One from each XML schema file.
  29. */
  30. protected $dataModels = [];
  31. /**
  32. * @var \Propel\Generator\Model\Database[]
  33. */
  34. protected $databases;
  35. /**
  36. * Map of data model name to database name.
  37. * Should probably stick to the convention
  38. * of them being the same but I know right now
  39. * in a lot of cases they won't be.
  40. *
  41. * @var array
  42. */
  43. protected $dataModelDbMap;
  44. /**
  45. * DB encoding to use for SchemaReader object
  46. *
  47. * @var string
  48. */
  49. protected $dbEncoding = 'UTF-8';
  50. /**
  51. * Whether to perform validation (XSD) on the schema.xml file(s).
  52. *
  53. * @var bool
  54. */
  55. protected $validate = false;
  56. /**
  57. * The XSD schema file to use for validation.
  58. *
  59. * @deprecated Not in use and not working due to missing class.
  60. *
  61. * @var mixed
  62. */
  63. protected $xsd;
  64. /**
  65. * XSL file to use to normalize (or otherwise transform) schema before validation.
  66. *
  67. * @deprecated Not in use and not working due to missing class.
  68. *
  69. * @var mixed
  70. */
  71. protected $xsl;
  72. /**
  73. * Gets list of all used xml schemas
  74. *
  75. * @var array
  76. */
  77. protected $schemas = [];
  78. /**
  79. * @var string
  80. */
  81. protected $workingDirectory;
  82. /**
  83. * @var \Closure|null
  84. */
  85. private $loggerClosure;
  86. /**
  87. * Have datamodels been initialized?
  88. *
  89. * @var bool
  90. */
  91. private $dataModelsLoaded = false;
  92. /**
  93. * An initialized GeneratorConfig object.
  94. *
  95. * @var \Propel\Generator\Config\GeneratorConfigInterface
  96. */
  97. private $generatorConfig;
  98. /**
  99. * Returns the list of schemas.
  100. *
  101. * @return array
  102. */
  103. public function getSchemas()
  104. {
  105. return $this->schemas;
  106. }
  107. /**
  108. * Sets the schemas list.
  109. *
  110. * @param array $schemas
  111. *
  112. * @return void
  113. */
  114. public function setSchemas($schemas)
  115. {
  116. $this->schemas = $schemas;
  117. }
  118. /**
  119. * Sets the working directory path.
  120. *
  121. * @param string $workingDirectory
  122. *
  123. * @return void
  124. */
  125. public function setWorkingDirectory($workingDirectory)
  126. {
  127. $this->workingDirectory = $workingDirectory;
  128. }
  129. /**
  130. * Returns the working directory path.
  131. *
  132. * @return string
  133. */
  134. public function getWorkingDirectory()
  135. {
  136. return $this->workingDirectory;
  137. }
  138. /**
  139. * Returns the data models that have been
  140. * processed.
  141. *
  142. * @return \Propel\Generator\Model\Schema[]
  143. */
  144. public function getDataModels()
  145. {
  146. if (!$this->dataModelsLoaded) {
  147. $this->loadDataModels();
  148. }
  149. return $this->dataModels;
  150. }
  151. /**
  152. * Returns the data model to database name map.
  153. *
  154. * @return array
  155. */
  156. public function getDataModelDbMap()
  157. {
  158. if (!$this->dataModelsLoaded) {
  159. $this->loadDataModels();
  160. }
  161. return $this->dataModelDbMap;
  162. }
  163. /**
  164. * @return \Propel\Generator\Model\Database[]
  165. */
  166. public function getDatabases()
  167. {
  168. if ($this->databases === null) {
  169. $databases = [];
  170. foreach ($this->getDataModels() as $dataModel) {
  171. foreach ($dataModel->getDatabases() as $database) {
  172. if (!isset($databases[$database->getName()])) {
  173. $databases[$database->getName()] = $database;
  174. } else {
  175. $tables = $database->getTables();
  176. // Merge tables from different schema.xml to the same database
  177. foreach ($tables as $table) {
  178. if (!$databases[$database->getName()]->hasTable($table->getName(), true)) {
  179. $databases[$database->getName()]->addTable($table);
  180. }
  181. }
  182. }
  183. }
  184. }
  185. $this->databases = $databases;
  186. }
  187. return $this->databases;
  188. }
  189. /**
  190. * @param string $name
  191. *
  192. * @return \Propel\Generator\Model\Database|null
  193. */
  194. public function getDatabase($name)
  195. {
  196. $dbs = $this->getDatabases();
  197. return $dbs[$name] ?? null;
  198. }
  199. /**
  200. * Sets whether to perform validation on the datamodel schema.xml file(s).
  201. *
  202. * @param bool $validate
  203. *
  204. * @return void
  205. */
  206. public function setValidate($validate)
  207. {
  208. $this->validate = (bool)$validate;
  209. }
  210. /**
  211. * Sets the XSD schema to use for validation of any datamodel schema.xml
  212. * file(s).
  213. *
  214. * @param string $xsd
  215. *
  216. * @return void
  217. */
  218. public function setXsd($xsd)
  219. {
  220. $this->xsd = $xsd;
  221. }
  222. /**
  223. * Sets the normalization XSLT to use to transform datamodel schema.xml
  224. * file(s) before validation and parsing.
  225. *
  226. * @param mixed $xsl
  227. *
  228. * @return void
  229. */
  230. public function setXsl($xsl)
  231. {
  232. $this->xsl = $xsl;
  233. }
  234. /**
  235. * Sets the current target database encoding.
  236. *
  237. * @param string $encoding Target database encoding
  238. *
  239. * @return void
  240. */
  241. public function setDbEncoding($encoding)
  242. {
  243. $this->dbEncoding = $encoding;
  244. }
  245. /**
  246. * Sets a logger closure.
  247. *
  248. * @param \Closure $logger
  249. *
  250. * @return void
  251. */
  252. public function setLoggerClosure(Closure $logger)
  253. {
  254. $this->loggerClosure = $logger;
  255. }
  256. /**
  257. * Returns all matching XML schema files and loads them into data models for
  258. * class.
  259. *
  260. * @throws \Propel\Generator\Exception\EngineException
  261. * @throws \Propel\Generator\Exception\BuildException
  262. *
  263. * @return void
  264. */
  265. protected function loadDataModels()
  266. {
  267. $schemas = [];
  268. $totalNbTables = 0;
  269. $dataModelFiles = $this->getSchemas();
  270. $defaultPlatform = $this->getGeneratorConfig()->getConfiguredPlatform();
  271. // Make a transaction for each file
  272. foreach ($dataModelFiles as $schema) {
  273. $dmFilename = $schema->getPathName();
  274. $this->log('Processing: ' . $schema->getFileName());
  275. $dom = new DOMDocument('1.0', 'UTF-8');
  276. $dom->load($dmFilename);
  277. $this->includeExternalSchemas($dom, $schema->getPath());
  278. // normalize (or transform) the XML document using XSLT
  279. if ($this->getGeneratorConfig()->get()['generator']['schema']['transform'] && $this->xsl) {
  280. $this->log('Transforming ' . $dmFilename . ' using stylesheet ' . $this->xsl->getPath());
  281. if (!class_exists('\XSLTProcessor')) {
  282. $this->log('Could not perform XLST transformation. Make sure PHP has been compiled/configured to support XSLT.');
  283. } else {
  284. // normalize the document using normalizer stylesheet
  285. $xslDom = new DOMDocument('1.0', 'UTF-8');
  286. $xslDom->load($this->xsl->getAbsolutePath());
  287. $xsl = new XsltProcessor();
  288. $xsl->importStyleSheet($xslDom);
  289. $dom = $xsl->transformToDoc($dom);
  290. }
  291. }
  292. // validate the XML document using XSD schema
  293. if ($this->validate && $this->xsd) {
  294. $this->log(' Validating XML using schema ' . $this->xsd->getPath());
  295. if (!$dom->schemaValidate($this->xsd->getAbsolutePath())) {
  296. throw new EngineException(sprintf("XML schema file (%s) 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).", $dmFilename), $this->getLocation());
  297. }
  298. }
  299. $xmlParser = new SchemaReader($defaultPlatform, $this->dbEncoding);
  300. $xmlParser->setGeneratorConfig($this->getGeneratorConfig());
  301. $schema = $xmlParser->parseString($dom->saveXML(), $dmFilename);
  302. $nbTables = $schema->getDatabase(null, false)->countTables();
  303. $totalNbTables += $nbTables;
  304. $this->log(sprintf(' %d tables processed successfully', $nbTables));
  305. $schema->setName($dmFilename);
  306. $schemas[] = $schema;
  307. }
  308. $this->log(sprintf('%d tables found in %d schema files.', $totalNbTables, count($dataModelFiles)));
  309. if (empty($schemas)) {
  310. throw new BuildException('No schema files were found (matching your schema fileset definition).');
  311. }
  312. foreach ($schemas as $schema) {
  313. // map schema filename with database name
  314. $this->dataModelDbMap[$schema->getName()] = $schema->getDatabase(null, false)->getName();
  315. }
  316. if (count($schemas) > 1 && $this->getGeneratorConfig()->get()['generator']['packageObjectModel']) {
  317. $schema = $this->joinDataModels($schemas);
  318. $this->dataModels = [$schema];
  319. } else {
  320. $this->dataModels = $schemas;
  321. }
  322. foreach ($this->dataModels as &$schema) {
  323. $schema->doFinalInitialization();
  324. }
  325. $this->dataModelsLoaded = true;
  326. }
  327. /**
  328. * Replaces all external-schema nodes with the content of xml schema that node refers to
  329. *
  330. * Recurses to include any external schema referenced from in an included xml (and deeper)
  331. * Note: this function very much assumes at least a reasonable XML schema, maybe it'll proof
  332. * users don't have those and adding some more informative exceptions would be better
  333. *
  334. * @param \DOMDocument $dom
  335. * @param string $srcDir
  336. *
  337. * @throws \Propel\Generator\Exception\BuildException
  338. *
  339. * @return int number of included external schemas
  340. */
  341. protected function includeExternalSchemas(DOMDocument $dom, $srcDir)
  342. {
  343. $databaseNode = $dom->getElementsByTagName('database')->item(0);
  344. $externalSchemaNodes = $dom->getElementsByTagName('external-schema');
  345. $nbIncludedSchemas = 0;
  346. while ($externalSchema = $externalSchemaNodes->item(0)) {
  347. $filePath = $externalSchema->getAttribute('filename');
  348. $referenceOnly = $externalSchema->getAttribute('referenceOnly');
  349. $this->log('Processing external schema: ' . $filePath);
  350. $externalSchema->parentNode->removeChild($externalSchema);
  351. $externalSchemaPath = realpath($srcDir . DIRECTORY_SEPARATOR . $filePath);
  352. if ($externalSchemaPath === false) {
  353. $externalSchemaPath = realpath($filePath);
  354. }
  355. if ($externalSchemaPath === false || !is_readable($externalSchemaPath)) {
  356. throw new BuildException("Cannot read external schema at '$filePath'");
  357. }
  358. $externalSchemaDom = new DOMDocument('1.0', 'UTF-8');
  359. $externalSchemaDom->load($externalSchemaPath);
  360. $this->includeExternalSchemas($externalSchemaDom, dirname($externalSchemaPath));
  361. foreach ($externalSchemaDom->getElementsByTagName('table') as $tableNode) {
  362. if ($referenceOnly === 'true') {
  363. $tableNode->setAttribute('skipSql', 'true');
  364. }
  365. $databaseNode->appendChild($dom->importNode($tableNode, true));
  366. }
  367. $nbIncludedSchemas++;
  368. }
  369. return $nbIncludedSchemas;
  370. }
  371. /**
  372. * Joins the datamodels collected from schema.xml files into one big datamodel.
  373. * We need to join the datamodels in this case to allow for foreign keys
  374. * that point to tables in different packages.
  375. *
  376. * @param array $schemas
  377. *
  378. * @return \Propel\Generator\Model\Schema
  379. */
  380. protected function joinDataModels(array $schemas)
  381. {
  382. $mainSchema = array_shift($schemas);
  383. $mainSchema->joinSchemas($schemas);
  384. return $mainSchema;
  385. }
  386. /**
  387. * Returns the GeneratorConfig object for this manager or creates it
  388. * on-demand.
  389. *
  390. * @return \Propel\Generator\Config\GeneratorConfigInterface
  391. */
  392. protected function getGeneratorConfig()
  393. {
  394. return $this->generatorConfig;
  395. }
  396. /**
  397. * Sets the GeneratorConfigInterface implementation.
  398. *
  399. * @param \Propel\Generator\Config\GeneratorConfigInterface $generatorConfig
  400. *
  401. * @return void
  402. */
  403. public function setGeneratorConfig(GeneratorConfigInterface $generatorConfig)
  404. {
  405. $this->generatorConfig = $generatorConfig;
  406. }
  407. /**
  408. * @throws \Propel\Generator\Exception\BuildException
  409. *
  410. * @return void
  411. */
  412. protected function validate()
  413. {
  414. if ($this->validate) {
  415. if (!$this->xsd) {
  416. throw new BuildException("'validate' set to TRUE, but no XSD specified (use 'xsd' attribute).");
  417. }
  418. }
  419. }
  420. /**
  421. * @param string $message
  422. *
  423. * @return void
  424. */
  425. protected function log($message)
  426. {
  427. if ($this->loggerClosure !== null) {
  428. $closure = $this->loggerClosure;
  429. $closure($message);
  430. }
  431. }
  432. /**
  433. * Returns an array of properties as key/value pairs from an input file.
  434. *
  435. * @param string $file
  436. *
  437. * @throws \Exception
  438. *
  439. * @return string[]
  440. */
  441. protected function getProperties($file)
  442. {
  443. $properties = [];
  444. if (false === $lines = @file($file)) {
  445. throw new Exception(sprintf('Unable to parse contents of "%s".', $file));
  446. }
  447. foreach ($lines as $line) {
  448. $line = trim($line);
  449. if (empty($line) || in_array($line[0], ['#', ';'])) {
  450. continue;
  451. }
  452. $pos = strpos($line, '=');
  453. $properties[trim(substr($line, 0, $pos))] = trim(substr($line, $pos + 1));
  454. }
  455. return $properties;
  456. }
  457. }