Spaces:
Sleeping
Sleeping
processmaker-community / processmaker-files /thirdparty /propel-generator /classes /propel /phing /AbstractPropelDataModelTask.php
| /* | |
| * $Id: AbstractPropelDataModelTask.php 536 2007-01-10 14:30:38Z heltem $ | |
| * | |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| * | |
| * This software consists of voluntary contributions made by many individuals | |
| * and is licensed under the LGPL. For more information please see | |
| * <http://propel.phpdb.org>. | |
| */ | |
| //include_once 'phing/tasks/ext/CapsuleTask.php'; | |
| require_once 'phing/TaskPhing.php'; | |
| include_once 'propel/engine/database/model/AppData.php'; | |
| include_once 'propel/engine/database/model/DatabasePropel.php'; | |
| include_once 'propel/engine/database/transform/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.phing | |
| */ | |
| abstract class AbstractPropelDataModelTask extends TaskPhing { | |
| /** | |
| * 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; | |
| /** | |
| * Hashtable containing the names of all the databases | |
| * in our collection of schemas. | |
| */ | |
| protected $databaseNames; // doesn't seem to be used anywhere | |
| /** | |
| * 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; | |
| /** | |
| * Path where Capsule looks for templates. | |
| * @var PhingFile | |
| */ | |
| protected $templatePath; | |
| /** | |
| * 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; | |
| /** | |
| * 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 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; | |
| } | |
| /** | |
| * [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; | |
| } | |
| /** | |
| * 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; | |
| } | |
| /** | |
| * Get the Platform class based on the target database type. | |
| * @return Platform Class that implements the Platform interface. | |
| */ | |
| protected function getPlatformForTargetDatabase() | |
| { | |
| $classpath = $this->getPropelProperty("platformClass"); | |
| if (empty($classpath)) { | |
| throw new BuildException("Unable to find class path for '$propname' property."); | |
| } | |
| // This is a slight hack to workaround camel case inconsistencies for the DDL classes. | |
| // Basically, we want to turn ?.?.?.sqliteDDLBuilder into ?.?.?.SqliteDDLBuilder | |
| $lastdotpos = strrpos($classpath, '.'); | |
| if ($lastdotpos) $classpath{$lastdotpos+1} = strtoupper($classpath{$lastdotpos+1}); | |
| else ucfirst($classpath); | |
| if (empty($classpath)) { | |
| throw new BuildException("Unable to find class path for '$propname' property."); | |
| } | |
| $clazz = Phing::import($classpath); | |
| return new $clazz(); | |
| } | |
| /** | |
| * 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->getPlatformForTargetDatabase(); | |
| // 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()); | |
| // normalize (or transform) the XML document using XSLT | |
| if ($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 | |
| $xslStyleSheet = new DOMDocument(); | |
| $xslStyleSheet->load($this->xslFile->getAbsolutePath()); | |
| $xsl = new XsltProcessor(); | |
| $xsl->importStyleSheet($xslStyleSheet); | |
| $transformed = $xsl->transformToDoc($dom); | |
| $newXmlFilename = substr($xmlFile->getName(), 0, strrpos($xmlFile->getName(), '.')) . '-transformed.xml'; | |
| // now overwrite previous vars to point to newly transformed file | |
| $xmlFile = new PhingFile($srcDir, $newXmlFilename); | |
| $transformed->save($xmlFile->getAbsolutePath()); | |
| $this->log("\t- Using new (post-transformation) XML file: " . $xmlFile->getPath(), PROJECT_MSG_VERBOSE); | |
| $dom = new DomDocument('1.0', 'UTF-8'); | |
| $dom->load($xmlFile->getAbsolutePath()); | |
| } | |
| } | |
| // 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 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()); | |
| } | |
| } | |
| $xmlParser = new XmlToAppData($platform, $this->getTargetPackage(), $this->dbEncoding); | |
| $ad = $xmlParser->parseFile($xmlFile->getAbsolutePath()); | |
| $ad->setName($dmFilename); // <-- Important: use the original name, not the -transformed name. | |
| $ads[] = $ad; | |
| } | |
| } | |
| if (empty($ads)) { | |
| throw new BuildException("No schema files were found (matching your schema fileset definition)."); | |
| } | |
| if (!$this->packageObjectModel) { | |
| $this->dataModels = $ads; | |
| $this->databaseNames = array(); // doesn't seem to be used anywhere | |
| $this->dataModelDbMap = array(); | |
| // Different datamodels may state the same database | |
| // names, we just want the unique names of databases. | |
| foreach($this->dataModels as $dm) { | |
| $database = $dm->getDatabase(); | |
| $this->dataModelDbMap[$dm->getName()] = $database->getName(); | |
| $this->databaseNames[$database->getName()] = $database->getName(); // making list of *unique* dbnames. | |
| } | |
| } else { | |
| $this->joinDatamodels($ads); | |
| $this->dataModels[0]->getDatabases(); // calls doFinalInitialization() | |
| } | |
| $this->dataModelsLoaded = true; | |
| } | |
| /** | |
| * Joins the datamodels collected from schema.xml files into one big datamodel | |
| * | |
| * This applies only when the the packageObjectModel option is set. We need to | |
| * join the datamodels in this case to allow for foreign keys that point to | |
| * tables in different packages. | |
| * | |
| * @param array $ads The datamodels to join | |
| */ | |
| protected function joinDatamodels($ads) { | |
| foreach($ads as $ad) { | |
| $db = $ad->getDatabase(null, false); | |
| $this->dataModelDbMap[$ad->getName()] = $db->getName(); | |
| } | |
| foreach ($ads as $addAd) { | |
| $ad = &$this->dataModels[0]; | |
| if (!isset($ad)) { | |
| $addAd->setName('JoinedDataModel'); | |
| $ad = $addAd; | |
| continue; | |
| } | |
| foreach ($addAd->getDatabases(false) as $addDb) { | |
| $addDbName = $addDb->getName(); | |
| if (!$package = $addDb->getPackage()) { | |
| 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.'); | |
| } | |
| $db = $ad->getDatabase($addDbName, false); | |
| if (!$db) { | |
| $ad->addDatabase($addDb); | |
| continue; | |
| } | |
| foreach ($addDb->getTables() as $addTable) { | |
| $table = $db->getTable($addTable->getName()); | |
| if ($table) { | |
| throw new BuildException('Duplicate table found: ' . $addDbName . '.'); | |
| } | |
| $db->addTable($addTable); | |
| } | |
| } | |
| } | |
| } | |
| /** | |
| * 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->targetDatabase); | |
| $context->put("targetPackage", $this->targetPackage); | |
| $context->put("now", strftime("%c")); | |
| $this->log("Target database type: " . $this->targetDatabase); | |
| $this->log("Target package: " . $this->targetPackage); | |
| $this->log("Using template path: " . $this->templatePath); | |
| $this->log("Output directory: " . $this->outputDirectory); | |
| $context->setTemplatePath($this->templatePath); | |
| $context->setOutputDirectory($this->outputDirectory); | |
| $this->populateContextProperties($context); | |
| return $context; | |
| } | |
| /** | |
| * Fetches the propel.xxx properties from project, renaming the propel.xxx properties to just xxx. | |
| * | |
| * Also, renames any xxx.yyy properties to xxxYyy as PHP doesn't like the xxx.yyy syntax. | |
| * | |
| * @return array Assoc array of properties. | |
| */ | |
| protected function getPropelProperties() | |
| { | |
| $allProps = $this->getProject()->getProperties(); | |
| $renamedPropelProps = array(); | |
| foreach ($allProps as $key => $propValue) { | |
| if (strpos($key, "propel.") === 0) { | |
| $newKey = substr($key, strlen("propel.")); | |
| $j = strpos($newKey, '.'); | |
| while ($j !== false) { | |
| $newKey = substr($newKey, 0, $j) . ucfirst(substr($newKey, $j + 1)); | |
| $j = strpos($newKey, '.'); | |
| } | |
| $renamedPropelProps[$newKey] = $propValue; | |
| } | |
| } | |
| return $renamedPropelProps; | |
| } | |
| /** | |
| * Fetches a single propel.xxx property from project, using "converted" property names. | |
| * @see getPropelProperties() | |
| * @param string $name Name of property to fetch (in converted CamelCase) | |
| * @return string The value of the property (or NULL if not set) | |
| */ | |
| protected function getPropelProperty($name) | |
| { | |
| $props = $this->getPropelProperties(); | |
| if (isset($props[$name])) { | |
| return $props[$name]; | |
| } | |
| return null; // just to be explicit | |
| } | |
| /** | |
| * Adds the propel.xxx properties to the passed Capsule context, changing names to just xxx. | |
| * | |
| * Also, move xxx.yyy properties to xxxYyy as PHP doesn't like the xxx.yyy syntax. | |
| * | |
| * @param Capsule $context | |
| * @see getPropelProperties() | |
| */ | |
| public function populateContextProperties(Capsule $context) | |
| { | |
| foreach ($this->getPropelProperties() as $key => $propValue) { | |
| $this->log('Adding property ${' . $key . '} to context', PROJECT_MSG_DEBUG); | |
| $context->put($key, $propValue); | |
| } | |
| } | |
| /** | |
| * 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()); | |
| } | |
| } | |
| } | |
| } | |