PageRenderTime 51ms CodeModel.GetById 13ms RepoModel.GetById 1ms app.codeStats 0ms

/core/xpdo/om/xpdogenerator.class.php

http://github.com/modxcms/revolution
PHP | 773 lines | 547 code | 24 blank | 202 comment | 116 complexity | 11bcaebc001fb4dd9787acaaf7a84af4 MD5 | raw file
Possible License(s): GPL-2.0, Apache-2.0, BSD-3-Clause, LGPL-2.1
  1. <?php
  2. /*
  3. * Copyright 2010-2015 by MODX, LLC.
  4. *
  5. * This file is part of xPDO.
  6. *
  7. * xPDO is free software; you can redistribute it and/or modify it under the
  8. * terms of the GNU General Public License as published by the Free Software
  9. * Foundation; either version 2 of the License, or (at your option) any later
  10. * version.
  11. *
  12. * xPDO is distributed in the hope that it will be useful, but WITHOUT ANY
  13. * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
  14. * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU General Public License along with
  17. * xPDO; if not, write to the Free Software Foundation, Inc., 59 Temple Place,
  18. * Suite 330, Boston, MA 02111-1307 USA
  19. */
  20. /**
  21. * Class for reverse and forward engineering xPDO domain models.
  22. *
  23. * @package xpdo
  24. * @subpackage om
  25. */
  26. /**
  27. * A service for reverse and forward engineering xPDO domain models.
  28. *
  29. * This service utilizes an xPDOManager instance to generate class stub and
  30. * meta-data map files from a provided vanilla XML schema of a database
  31. * structure. It can also reverse-engineer XML schemas from an existing
  32. * database.
  33. *
  34. * @abstract
  35. * @package xpdo
  36. * @subpackage om
  37. */
  38. abstract class xPDOGenerator {
  39. /**
  40. * @var xPDOManager $manager A reference to the xPDOManager using this
  41. * generator.
  42. */
  43. public $manager= null;
  44. /**
  45. * @var xPDOSchemaManager $schemaManager
  46. */
  47. public $schemaManager= null;
  48. /**
  49. * @var string $outputDir The absolute path to output the class and map
  50. * files to.
  51. */
  52. public $outputDir= '';
  53. /**
  54. * @var string $schemaFile An absolute path to the schema file.
  55. */
  56. public $schemaFile= '';
  57. /**
  58. * @var string $schemaContent The stored content of the newly-created schema
  59. * file.
  60. */
  61. public $schemaContent= '';
  62. /**
  63. * @var string $classTemplate The class template string to build the class
  64. * files from.
  65. */
  66. public $classTemplate= '';
  67. /**
  68. * @var string $platformTemplate The class platform template string to build
  69. * the class platform files from.
  70. */
  71. public $platformTemplate= '';
  72. /**
  73. * @var string $metaTemplate The class platform template string to build
  74. * the meta class map files from.
  75. */
  76. public $metaTemplate= '';
  77. /**
  78. * @var string $mapHeader The map header string to build the map files from.
  79. */
  80. public $mapHeader= '';
  81. /**
  82. * @var string $mapFooter The map footer string to build the map files from.
  83. */
  84. public $mapFooter= '';
  85. /**
  86. * @var array $model The stored model array.
  87. */
  88. public $model= array ();
  89. /**
  90. * @var array $classes The stored classes array.
  91. */
  92. public $classes= array ();
  93. /**
  94. * @var array $map The stored map array.
  95. */
  96. public $map= array ();
  97. /**
  98. * @var SimpleXMLElement
  99. */
  100. public $schema= null;
  101. /**
  102. * Constructor
  103. *
  104. * @access protected
  105. * @param xPDOManager &$manager A reference to a valid xPDOManager instance.
  106. * @return xPDOGenerator
  107. */
  108. public function __construct(& $manager) {
  109. $this->manager= & $manager;
  110. }
  111. /**
  112. * Formats a class name to a specific value, stripping the prefix if
  113. * specified.
  114. *
  115. * @access public
  116. * @param string $string The name to format.
  117. * @param string $prefix If specified, will strip the prefix out of the
  118. * first argument.
  119. * @param boolean $prefixRequired If true, will return a blank string if the
  120. * prefix specified is not found.
  121. * @return string The formatting string.
  122. */
  123. public function getTableName($string, $prefix= '', $prefixRequired= false) {
  124. if (!empty($prefix) && strpos($string, $prefix) === 0) {
  125. $string= substr($string, strlen($prefix));
  126. }
  127. elseif ($prefixRequired) {
  128. $string= '';
  129. }
  130. return $string;
  131. }
  132. /**
  133. * Gets a class name from a table name by splitting the string by _ and
  134. * capitalizing each token.
  135. *
  136. * @access public
  137. * @param string $string The table name to format.
  138. * @return string The formatted string.
  139. */
  140. public function getClassName($string) {
  141. if (is_string($string) && $strArray= explode('_', $string)) {
  142. $return= '';
  143. foreach ($strArray as $k => $v) {
  144. $return.= strtoupper(substr($v, 0, 1)) . substr($v, 1) . '';
  145. }
  146. $string= $return;
  147. }
  148. return trim($string);
  149. }
  150. /**
  151. * Format the passed default value as an XML attribute.
  152. *
  153. * Override this in different PDO driver implementations if necessary.
  154. *
  155. * @access public
  156. * @param string $value The value to encapsulate in the default tag.
  157. * @return string The parsed XML string
  158. */
  159. public function getDefault($value) {
  160. $return= '';
  161. if ($value !== null) {
  162. $return= ' default="'.$value.'"';
  163. }
  164. return $return;
  165. }
  166. /**
  167. * Format the passed database index value as an XML attribute.
  168. *
  169. * @abstract Implement this for specific PDO driver implementations.
  170. * @access public
  171. * @param string $index The DB representation string of the index
  172. * @return string The formatted XML attribute string
  173. */
  174. abstract public function getIndex($index);
  175. /**
  176. * Parses an XPDO XML schema and generates classes and map files from it.
  177. *
  178. * Requires SimpleXML for parsing an XML schema.
  179. *
  180. * @param string $schemaFile The name of the XML file representing the
  181. * schema.
  182. * @param string $outputDir The directory in which to generate the class and
  183. * map files into.
  184. * @param boolean $compile Create compiled copies of the classes and maps from the schema.
  185. * @return boolean True on success, false on failure.
  186. */
  187. public function parseSchema($schemaFile, $outputDir= '', $compile= false) {
  188. $this->schemaFile= $schemaFile;
  189. $this->classTemplate= $this->getClassTemplate();
  190. if (!is_file($schemaFile)) {
  191. $this->manager->xpdo->log(xPDO::LOG_LEVEL_ERROR, "Could not find specified XML schema file {$schemaFile}");
  192. return false;
  193. }
  194. $this->schema = new SimpleXMLElement($schemaFile, 0, true);
  195. if (isset($this->schema)) {
  196. foreach ($this->schema->attributes() as $attributeKey => $attribute) {
  197. /** @var SimpleXMLElement $attribute */
  198. $this->model[$attributeKey] = (string) $attribute;
  199. }
  200. if (isset($this->schema->object)) {
  201. foreach ($this->schema->object as $object) {
  202. /** @var SimpleXMLElement $object */
  203. $class = (string) $object['class'];
  204. $extends = isset($object['extends']) ? (string) $object['extends'] : $this->model['baseClass'];
  205. $this->classes[$class] = array('extends' => $extends);
  206. $this->map[$class] = array(
  207. 'package' => $this->model['package'],
  208. 'version' => $this->model['version']
  209. );
  210. foreach ($object->attributes() as $objAttrKey => $objAttr) {
  211. if ($objAttrKey == 'class') continue;
  212. $this->map[$class][$objAttrKey]= (string) $objAttr;
  213. if (!in_array($objAttrKey, array('package', 'version', 'extends', 'table'))) {
  214. $this->classes[$class][$objAttrKey] = (string) $objAttr;
  215. }
  216. }
  217. $engine = (string) $object['engine'];
  218. if (!empty($engine)) {
  219. $this->map[$class]['tableMeta'] = array('engine' => $engine);
  220. } elseif (isset($this->model['defaultEngine'])) {
  221. $this->map[$class]['tableMeta'] = array('engine' => $this->model['defaultEngine']);
  222. }
  223. $this->map[$class]['fields']= array();
  224. $this->map[$class]['fieldMeta']= array();
  225. if (isset($object->field)) {
  226. foreach ($object->field as $field) {
  227. $key = (string) $field['key'];
  228. $dbtype = (string) $field['dbtype'];
  229. $defaultType = $this->manager->xpdo->driver->getPhpType($dbtype);
  230. $this->map[$class]['fields'][$key]= null;
  231. $this->map[$class]['fieldMeta'][$key]= array();
  232. foreach ($field->attributes() as $fldAttrKey => $fldAttr) {
  233. $fldAttrValue = (string) $fldAttr;
  234. switch ($fldAttrKey) {
  235. case 'key':
  236. continue 2;
  237. case 'default':
  238. if ($fldAttrValue === 'NULL') {
  239. $fldAttrValue = null;
  240. }
  241. switch ($defaultType) {
  242. case 'integer':
  243. case 'boolean':
  244. case 'bit':
  245. $fldAttrValue = (integer) $fldAttrValue;
  246. break;
  247. case 'float':
  248. case 'numeric':
  249. $fldAttrValue = (float) $fldAttrValue;
  250. break;
  251. default:
  252. break;
  253. }
  254. $this->map[$class]['fields'][$key]= $fldAttrValue;
  255. break;
  256. case 'null':
  257. $fldAttrValue = (!empty($fldAttrValue) && strtolower($fldAttrValue) !== 'false') ? true : false;
  258. break;
  259. default:
  260. break;
  261. }
  262. $this->map[$class]['fieldMeta'][$key][$fldAttrKey]= $fldAttrValue;
  263. }
  264. }
  265. }
  266. if (isset($object->alias)) {
  267. $this->map[$class]['fieldAliases'] = array();
  268. foreach ($object->alias as $alias) {
  269. $aliasKey = (string) $alias['key'];
  270. $aliasNode = array();
  271. foreach ($alias->attributes() as $attrName => $attr) {
  272. $attrValue = (string) $attr;
  273. switch ($attrName) {
  274. case 'key':
  275. continue 2;
  276. case 'field':
  277. $aliasNode = $attrValue;
  278. break;
  279. default:
  280. break;
  281. }
  282. }
  283. if (!empty($aliasKey) && !empty($aliasNode)) {
  284. $this->map[$class]['fieldAliases'][$aliasKey] = $aliasNode;
  285. }
  286. }
  287. }
  288. if (isset($object->index)) {
  289. $this->map[$class]['indexes'] = array();
  290. foreach ($object->index as $index) {
  291. $indexNode = array();
  292. $indexName = (string) $index['name'];
  293. foreach ($index->attributes() as $attrName => $attr) {
  294. $attrValue = (string) $attr;
  295. switch ($attrName) {
  296. case 'name':
  297. continue 2;
  298. case 'primary':
  299. case 'unique':
  300. case 'fulltext':
  301. $attrValue = (empty($attrValue) || $attrValue === 'false' ? false : true);
  302. default:
  303. $indexNode[$attrName] = $attrValue;
  304. break;
  305. }
  306. }
  307. if (!empty($indexNode) && isset($index->column)) {
  308. $indexNode['columns']= array();
  309. foreach ($index->column as $column) {
  310. $columnKey = (string) $column['key'];
  311. $indexNode['columns'][$columnKey] = array();
  312. foreach ($column->attributes() as $attrName => $attr) {
  313. $attrValue = (string) $attr;
  314. switch ($attrName) {
  315. case 'key':
  316. continue 2;
  317. case 'null':
  318. $attrValue = (empty($attrValue) || $attrValue === 'false' ? false : true);
  319. default:
  320. $indexNode['columns'][$columnKey][$attrName]= $attrValue;
  321. break;
  322. }
  323. }
  324. }
  325. if (!empty($indexNode['columns'])) {
  326. $this->map[$class]['indexes'][$indexName]= $indexNode;
  327. }
  328. }
  329. }
  330. }
  331. if (isset($object->composite)) {
  332. $this->map[$class]['composites'] = array();
  333. foreach ($object->composite as $composite) {
  334. $compositeNode = array();
  335. $compositeAlias = (string) $composite['alias'];
  336. foreach ($composite->attributes() as $attrName => $attr) {
  337. $attrValue = (string) $attr;
  338. switch ($attrName) {
  339. case 'alias' :
  340. continue 2;
  341. case 'criteria' :
  342. $attrValue = $this->manager->xpdo->fromJSON(urldecode($attrValue));
  343. default :
  344. $compositeNode[$attrName]= $attrValue;
  345. break;
  346. }
  347. }
  348. if (!empty($compositeNode)) {
  349. if (isset($composite->criteria)) {
  350. /** @var SimpleXMLElement $criteria */
  351. foreach ($composite->criteria as $criteria) {
  352. $criteriaTarget = (string) $criteria['target'];
  353. $expression = (string) $criteria;
  354. if (!empty($expression)) {
  355. $expression = $this->manager->xpdo->fromJSON($expression);
  356. if (!empty($expression)) {
  357. if (!isset($compositeNode['criteria'])) $compositeNode['criteria'] = array();
  358. if (!isset($compositeNode['criteria'][$criteriaTarget])) $compositeNode['criteria'][$criteriaTarget] = array();
  359. $compositeNode['criteria'][$criteriaTarget] = array_merge($compositeNode['criteria'][$criteriaTarget], (array) $expression);
  360. }
  361. }
  362. }
  363. }
  364. $this->map[$class]['composites'][$compositeAlias] = $compositeNode;
  365. }
  366. }
  367. }
  368. if (isset($object->aggregate)) {
  369. $this->map[$class]['aggregates'] = array();
  370. foreach ($object->aggregate as $aggregate) {
  371. $aggregateNode = array();
  372. $aggregateAlias = (string) $aggregate['alias'];
  373. foreach ($aggregate->attributes() as $attrName => $attr) {
  374. $attrValue = (string) $attr;
  375. switch ($attrName) {
  376. case 'alias' :
  377. continue 2;
  378. case 'criteria' :
  379. $attrValue = $this->manager->xpdo->fromJSON(urldecode($attrValue));
  380. default :
  381. $aggregateNode[$attrName]= $attrValue;
  382. break;
  383. }
  384. }
  385. if (!empty($aggregateNode)) {
  386. if (isset($aggregate->criteria)) {
  387. /** @var SimpleXMLElement $criteria */
  388. foreach ($aggregate->criteria as $criteria) {
  389. $criteriaTarget = (string) $criteria['target'];
  390. $expression = (string) $criteria;
  391. if (!empty($expression)) {
  392. $expression = $this->manager->xpdo->fromJSON($expression);
  393. if (!empty($expression)) {
  394. if (!isset($aggregateNode['criteria'])) $aggregateNode['criteria'] = array();
  395. if (!isset($aggregateNode['criteria'][$criteriaTarget])) $aggregateNode['criteria'][$criteriaTarget] = array();
  396. $aggregateNode['criteria'][$criteriaTarget] = array_merge($aggregateNode['criteria'][$criteriaTarget], (array) $expression);
  397. }
  398. }
  399. }
  400. }
  401. $this->map[$class]['aggregates'][$aggregateAlias] = $aggregateNode;
  402. }
  403. }
  404. }
  405. if (isset($object->validation)) {
  406. $this->map[$class]['validation'] = array();
  407. $validation = $object->validation[0];
  408. $validationNode = array();
  409. foreach ($validation->attributes() as $attrName => $attr) {
  410. $validationNode[$attrName]= (string) $attr;
  411. }
  412. if (isset($validation->rule)) {
  413. $validationNode['rules'] = array();
  414. foreach ($validation->rule as $rule) {
  415. $ruleNode = array();
  416. $field= (string) $rule['field'];
  417. $name= (string) $rule['name'];
  418. foreach ($rule->attributes() as $attrName => $attr) {
  419. $attrValue = (string) $attr;
  420. switch ($attrName) {
  421. case 'field' :
  422. case 'name' :
  423. continue 2;
  424. default :
  425. $ruleNode[$attrName]= $attrValue;
  426. break;
  427. }
  428. }
  429. if (!empty($field) && !empty($name) && !empty($ruleNode)) {
  430. $validationNode['rules'][$field][$name]= $ruleNode;
  431. }
  432. }
  433. if (!empty($validationNode['rules'])) {
  434. $this->map[$class]['validation'] = $validationNode;
  435. }
  436. }
  437. }
  438. }
  439. } else {
  440. $this->manager->xpdo->log(xPDO::LOG_LEVEL_ERROR, "Schema {$schemaFile} contains no valid object elements.");
  441. }
  442. } else {
  443. $this->manager->xpdo->log(xPDO::LOG_LEVEL_ERROR, "Could not read schema from {$schemaFile}.");
  444. }
  445. $om_path= XPDO_CORE_PATH . 'om/';
  446. $path= !empty ($outputDir) ? $outputDir : $om_path;
  447. if (isset ($this->model['package']) && strlen($this->model['package']) > 0) {
  448. $path .= strtr($this->model['package'], '.', '/');
  449. $path .= '/';
  450. }
  451. $this->outputMeta($path);
  452. $this->outputClasses($path);
  453. $this->outputMaps($path);
  454. if ($compile) $this->compile($path, $this->model, $this->classes, $this->maps);
  455. unset($this->model, $this->classes, $this->map);
  456. return true;
  457. }
  458. /**
  459. * Write the generated class files to the specified path.
  460. *
  461. * @access public
  462. * @param string $path An absolute path to write the generated class files
  463. * to.
  464. */
  465. public function outputClasses($path) {
  466. $newClassGeneration= false;
  467. $newPlatformGeneration= false;
  468. $platform= $this->model['platform'];
  469. if (!is_dir($path)) {
  470. $newClassGeneration= true;
  471. if ($this->manager->xpdo->getCacheManager()) {
  472. if (!$this->manager->xpdo->cacheManager->writeTree($path)) {
  473. $this->manager->xpdo->log(xPDO::LOG_LEVEL_ERROR, "Could not create model directory at {$path}");
  474. return false;
  475. }
  476. }
  477. }
  478. $ppath= $path;
  479. $ppath .= $platform;
  480. if (!is_dir($ppath)) {
  481. $newPlatformGeneration= true;
  482. if ($this->manager->xpdo->getCacheManager()) {
  483. if (!$this->manager->xpdo->cacheManager->writeTree($ppath)) {
  484. $this->manager->xpdo->log(xPDO::LOG_LEVEL_ERROR, "Could not create platform subdirectory {$ppath}");
  485. return false;
  486. }
  487. }
  488. }
  489. $model= $this->model;
  490. if (isset($this->model['phpdoc-package'])) {
  491. $model['phpdoc-package']= '@package ' . $this->model['phpdoc-package'];
  492. if (isset($this->model['phpdoc-subpackage']) && !empty($this->model['phpdoc-subpackage'])) {
  493. $model['phpdoc-subpackage']= '@subpackage ' . $this->model['phpdoc-subpackage'] . '.' . $this->model['platform'];
  494. } else {
  495. $model['phpdoc-subpackage']= '@subpackage ' . $this->model['platform'];
  496. }
  497. } else {
  498. $basePos= strpos($this->model['package'], '.');
  499. $package= $basePos
  500. ? substr($this->model['package'], 0, $basePos)
  501. : $this->model['package'];
  502. $subpackage= $basePos
  503. ? substr($this->model['package'], $basePos + 1)
  504. : '';
  505. $model['phpdoc-package']= '@package ' . $package;
  506. if ($subpackage) $model['phpdoc-subpackage']= '@subpackage ' . $subpackage;
  507. }
  508. foreach ($this->classes as $className => $classDef) {
  509. $newClass= false;
  510. $classDef['class']= $className;
  511. $classDef['class-lowercase']= strtolower($className);
  512. $classDef= array_merge($model, $classDef);
  513. $replaceVars= array ();
  514. foreach ($classDef as $varKey => $varValue) {
  515. if (is_scalar($varValue)) $replaceVars["[+{$varKey}+]"]= $varValue;
  516. }
  517. $fileContent= str_replace(array_keys($replaceVars), array_values($replaceVars), $this->classTemplate);
  518. if (is_dir($path)) {
  519. $fileName= $path . strtolower($className) . '.class.php';
  520. if (!file_exists($fileName)) {
  521. if ($file= @ fopen($fileName, 'wb')) {
  522. if (!fwrite($file, $fileContent)) {
  523. $this->manager->xpdo->log(xPDO::LOG_LEVEL_ERROR, "Could not write to file: {$fileName}");
  524. }
  525. $newClass= true;
  526. @fclose($file);
  527. } else {
  528. $this->manager->xpdo->log(xPDO::LOG_LEVEL_ERROR, "Could not open or create file: {$fileName}");
  529. }
  530. } else {
  531. $newClass= false;
  532. $this->manager->xpdo->log(xPDO::LOG_LEVEL_INFO, "Skipping {$fileName}; file already exists.\nMove existing class files to regenerate them.");
  533. }
  534. } else {
  535. $this->manager->xpdo->log(xPDO::LOG_LEVEL_ERROR, "Could not open or create dir: {$path}");
  536. }
  537. $fileContent= str_replace(array_keys($replaceVars), array_values($replaceVars), $this->getClassPlatformTemplate($platform));
  538. if (is_dir($ppath)) {
  539. $fileName= $ppath . '/' . strtolower($className) . '.class.php';
  540. if (!file_exists($fileName)) {
  541. if ($file= @ fopen($fileName, 'wb')) {
  542. if (!fwrite($file, $fileContent)) {
  543. $this->manager->xpdo->log(xPDO::LOG_LEVEL_ERROR, "Could not write to file: {$fileName}");
  544. }
  545. @fclose($file);
  546. } else {
  547. $this->manager->xpdo->log(xPDO::LOG_LEVEL_ERROR, "Could not open or create file: {$fileName}");
  548. }
  549. } else {
  550. $this->manager->xpdo->log(xPDO::LOG_LEVEL_INFO, "Skipping {$fileName}; file already exists.\nMove existing class files to regenerate them.");
  551. if ($newClassGeneration || $newClass) $this->manager->xpdo->log(xPDO::LOG_LEVEL_WARN, "IMPORTANT: {$fileName} already exists but you appear to have generated classes with an older xPDO version. You need to edit your class definition in this file to extend {$className} rather than {$classDef['extends']}.");
  552. }
  553. } else {
  554. $this->manager->xpdo->log(xPDO::LOG_LEVEL_ERROR, "Could not open or create dir: {$path}");
  555. }
  556. }
  557. }
  558. /**
  559. * Write the generated class maps to the specified path.
  560. *
  561. * @access public
  562. * @param string $path An absolute path to write the generated maps to.
  563. */
  564. public function outputMaps($path) {
  565. if (!is_dir($path)) {
  566. mkdir($path, 0777);
  567. }
  568. $path .= $this->model['platform'];
  569. if (!is_dir($path)) {
  570. mkdir($path, 0777);
  571. }
  572. $model= $this->model;
  573. if (isset($this->model['phpdoc-package'])) {
  574. $model['phpdoc-package']= '@package ' . $this->model['phpdoc-package'];
  575. if (isset($this->model['phpdoc-subpackage']) && !empty($this->model['phpdoc-subpackage'])) {
  576. $model['phpdoc-subpackage']= '@subpackage ' . $this->model['phpdoc-subpackage'] . '.' . $this->model['platform'];
  577. } else {
  578. $model['phpdoc-subpackage']= '@subpackage ' . $this->model['platform'];
  579. }
  580. } else {
  581. $basePos= strpos($this->model['package'], '.');
  582. $package= $basePos
  583. ? substr($this->model['package'], 0, $basePos)
  584. : $this->model['package'];
  585. $subpackage= $basePos
  586. ? substr($this->model['package'], $basePos + 1) . '.' . $this->model['platform']
  587. : $this->model['platform'];
  588. $model['phpdoc-package']= '@package ' . $package;
  589. $model['phpdoc-subpackage']= '@subpackage ' . $subpackage;
  590. }
  591. foreach ($this->map as $className => $map) {
  592. $lcClassName= strtolower($className);
  593. $fileName= $path . '/' . strtolower($className) . '.map.inc.php';
  594. $vars= array_merge($model, $map);
  595. $replaceVars= array ();
  596. foreach ($vars as $varKey => $varValue) {
  597. if (is_scalar($varValue)) $replaceVars["[+{$varKey}+]"]= $varValue;
  598. }
  599. $fileContent= str_replace(array_keys($replaceVars), array_values($replaceVars), $this->getMapHeader());
  600. $fileContent.= "\n\$xpdo_meta_map['$className']= " . var_export($map, true) . ";\n";
  601. $fileContent.= str_replace(array_keys($replaceVars), array_values($replaceVars), $this->getMapFooter());
  602. if (is_dir($path)) {
  603. if ($file= @ fopen($fileName, 'wb')) {
  604. if (!fwrite($file, $fileContent)) {
  605. $this->manager->xpdo->log(xPDO::LOG_LEVEL_ERROR, "Could not write to file: {$fileName}");
  606. }
  607. fclose($file);
  608. } else {
  609. $this->manager->xpdo->log(xPDO::LOG_LEVEL_ERROR, "Could not open or create file: {$fileName}");
  610. }
  611. } else {
  612. $this->manager->xpdo->log(xPDO::LOG_LEVEL_ERROR, "Could not open or create dir: {$path}");
  613. }
  614. }
  615. }
  616. /**
  617. * Write the generated meta map to the specified path.
  618. *
  619. * @param string $path An absolute path to write the generated maps to.
  620. * @return bool
  621. */
  622. public function outputMeta($path) {
  623. if (!is_dir($path)) {
  624. if ($this->manager->xpdo->getCacheManager()) {
  625. if (!$this->manager->xpdo->cacheManager->writeTree($path)) {
  626. $this->manager->xpdo->log(xPDO::LOG_LEVEL_ERROR, "Could not create model directory at {$path}");
  627. return false;
  628. }
  629. }
  630. }
  631. $placeholders = array();
  632. $model= $this->model;
  633. if (isset($this->model['phpdoc-package'])) {
  634. $model['phpdoc-package']= '@package ' . $this->model['phpdoc-package'];
  635. if (isset($this->model['phpdoc-subpackage']) && !empty($this->model['phpdoc-subpackage'])) {
  636. $model['phpdoc-subpackage']= '@subpackage ' . $this->model['phpdoc-subpackage'] . '.' . $this->model['platform'];
  637. } else {
  638. $model['phpdoc-subpackage']= '@subpackage ' . $this->model['platform'];
  639. }
  640. } else {
  641. $basePos= strpos($this->model['package'], '.');
  642. $package= $basePos
  643. ? substr($this->model['package'], 0, $basePos)
  644. : $this->model['package'];
  645. $subpackage= $basePos
  646. ? substr($this->model['package'], $basePos + 1) . '.' . $this->model['platform']
  647. : $this->model['platform'];
  648. $model['phpdoc-package']= '@package ' . $package;
  649. $model['phpdoc-subpackage']= '@subpackage ' . $subpackage;
  650. }
  651. $placeholders = array_merge($placeholders,$model);
  652. $classMap = array();
  653. // $skipClasses = array('xPDOObject','xPDOSimpleObject');
  654. foreach ($this->classes as $className => $meta) {
  655. if (!isset($meta['extends'])) {
  656. $meta['extends'] = 'xPDOObject';
  657. }
  658. if (!isset($classMap[$meta['extends']])) {
  659. $classMap[$meta['extends']] = array();
  660. }
  661. $classMap[$meta['extends']][] = $className;
  662. }
  663. if ($this->manager->xpdo->getCacheManager()) {
  664. $placeholders['map'] = var_export($classMap,true);
  665. $replaceVars = array();
  666. foreach ($placeholders as $varKey => $varValue) {
  667. if (is_scalar($varValue)) $replaceVars["[+{$varKey}+]"]= $varValue;
  668. }
  669. $fileContent= str_replace(array_keys($replaceVars), array_values($replaceVars), $this->getMetaTemplate());
  670. $this->manager->xpdo->cacheManager->writeFile("{$path}/metadata.{$model['platform']}.php",$fileContent);
  671. }
  672. return true;
  673. }
  674. /**
  675. * Compile the packages into a single file for quicker loading.
  676. *
  677. * @abstract
  678. * @access public
  679. * @param string $path The absolute path to compile into.
  680. * @return boolean True if the compiling went successfully.
  681. */
  682. abstract public function compile($path= '');
  683. /**
  684. * Return the class template for the class files.
  685. *
  686. * @access public
  687. * @return string The class template.
  688. */
  689. public function getClassTemplate() {
  690. if ($this->classTemplate) return $this->classTemplate;
  691. $template= <<<EOD
  692. <?php
  693. class [+class+] extends [+extends+] {}
  694. EOD;
  695. return $template;
  696. }
  697. /**
  698. * Return the class platform template for the class files.
  699. *
  700. * @access public
  701. * @return string The class platform template.
  702. */
  703. public function getClassPlatformTemplate($platform) {
  704. if ($this->platformTemplate) return $this->platformTemplate;
  705. $template= <<<EOD
  706. <?php
  707. require_once (dirname(__DIR__) . '/[+class-lowercase+].class.php');
  708. class [+class+]_$platform extends [+class+] {}
  709. EOD;
  710. return $template;
  711. }
  712. /**
  713. * Gets the map header template.
  714. *
  715. * @access public
  716. * @return string The map header template.
  717. */
  718. public function getMapHeader() {
  719. if ($this->mapHeader) return $this->mapHeader;
  720. $header= <<<EOD
  721. <?php
  722. EOD;
  723. return $header;
  724. }
  725. /**
  726. * Gets the map footer template.
  727. *
  728. * @access public
  729. * @return string The map footer template.
  730. */
  731. public function getMapFooter() {
  732. if ($this->mapFooter) return $this->mapFooter;
  733. return '';
  734. }
  735. /**
  736. * Gets the meta template.
  737. *
  738. * @access public
  739. * @return string The meta template.
  740. */
  741. public function getMetaTemplate() {
  742. if ($this->metaTemplate) return $this->metaTemplate;
  743. $tpl= <<<EOD
  744. <?php
  745. \n\$xpdo_meta_map = [+map+];
  746. EOD;
  747. return $tpl;
  748. }
  749. }