3 * TSqlMapXmlConfigBuilder, TSqlMapXmlConfiguration, TSqlMapXmlMappingConfiguration classes file.
5 * @author Wei Zhuo <weizhuo[at]gmail[dot]com>
6 * @link http://www.pradosoft.com/
7 * @copyright Copyright © 2005-2013 PradoSoft
8 * @license http://www.pradosoft.com/license/
9 * @version $Id: TSqlMapXmlConfiguration.php 3245 2013-01-07 20:23:32Z ctrlaltca $
10 * @package System.Data.SqlMap.Configuration
13 Prado::using('System.Data.SqlMap.Configuration.TSqlMapStatement');
16 * TSqlMapXmlConfig class file.
18 * @author Wei Zhuo <weizhuo[at]gmail[dot]com>
19 * @version $Id: TSqlMapXmlConfiguration.php 3245 2013-01-07 20:23:32Z ctrlaltca $
20 * @package System.Data.SqlMap.Configuration
22 abstract class TSqlMapXmlConfigBuilder
25 * Create an instance of an object give by the attribute named 'class' in the
26 * node and set the properties on the object given by attribute names and values.
27 * @param SimpleXmlNode property node
28 * @return Object new instance of class with class name given by 'class' attribute value.
30 protected function createObjectFromNode($node)
32 if(isset($node['class']))
34 $obj = Prado::createComponent((string)$node['class']);
35 $this->setObjectPropFromNode($obj,$node,array('class'));
38 throw new TSqlMapConfigurationException(
39 'sqlmap_node_class_undef', $node, $this->getConfigFile());
43 * For each attributes (excluding attribute named in $except) set the
44 * property of the $obj given by the name of the attribute with the value
46 * @param Object object instance
47 * @param SimpleXmlNode property node
48 * @param array exception property name
50 protected function setObjectPropFromNode($obj,$node,$except=array())
52 foreach($node->attributes() as $name=>$value)
54 if(!in_array($name,$except))
56 if($obj->canSetProperty($name))
57 $obj->{$name} = (string)$value;
59 throw new TSqlMapConfigurationException(
60 'sqlmap_invalid_property', $name, get_class($obj),
61 $node, $this->getConfigFile());
67 * Gets the filename relative to the basefile.
68 * @param string base filename
69 * @param string relative filename
70 * @return string absolute filename.
72 protected function getAbsoluteFilePath($basefile,$resource)
74 $basedir = dirname($basefile);
75 $file = realpath($basedir.DIRECTORY_SEPARATOR.$resource);
76 if(!is_string($file) || !is_file($file))
77 $file = realpath($resource);
78 if(is_string($file) && is_file($file))
81 throw new TSqlMapConfigurationException(
82 'sqlmap_unable_to_find_resource', $resource);
86 * Load document using simple xml.
87 * @param string filename.
88 * @return SimpleXmlElement xml document.
90 protected function loadXmlDocument($filename,TSqlMapXmlConfiguration $config)
92 if( strpos($filename, '${') !== false)
93 $filename = $config->replaceProperties($filename);
95 if(!is_file($filename))
96 throw new TSqlMapConfigurationException(
97 'sqlmap_unable_to_find_config', $filename);
98 return simplexml_load_string($config->replaceProperties(file_get_contents($filename)));
102 * Get element node by ID value (try for attribute name ID as case insensitive).
103 * @param SimpleXmlDocument $document
104 * @param string tag name.
105 * @param string id value.
106 * @return SimpleXmlElement node if found, null otherwise.
108 protected function getElementByIdValue($document, $tag, $value)
110 //hack to allow upper case and lower case attribute names.
111 foreach(array('id','ID','Id', 'iD') as $id)
113 $xpath = "//{$tag}[@{$id}='{$value}']";
114 foreach($document->xpath($xpath) as $node)
120 * @return string configuration file.
122 protected abstract function getConfigFile();
126 * TSqlMapXmlConfig class.
128 * Configures the TSqlMapManager using xml configuration file.
130 * @author Wei Zhuo <weizho[at]gmail[dot]com>
131 * @version $Id: TSqlMapXmlConfiguration.php 3245 2013-01-07 20:23:32Z ctrlaltca $
132 * @package System.Data.SqlMap.Configuration
135 class TSqlMapXmlConfiguration extends TSqlMapXmlConfigBuilder
138 * @var TSqlMapManager manager
142 * @var string configuration file.
144 private $_configFile;
146 * @var array global properties.
148 private $_properties=array();
151 * @param TSqlMapManager manager instance.
153 public function __construct($manager)
155 $this->_manager=$manager;
158 public function getManager()
160 return $this->_manager;
163 protected function getConfigFile()
165 return $this->_configFile;
169 * Configure the TSqlMapManager using the given xml file.
170 * @param string SqlMap configuration xml file.
172 public function configure($filename=null)
174 $this->_configFile=$filename;
175 $document = $this->loadXmlDocument($filename,$this);
177 foreach($document->xpath('//property') as $property)
178 $this->loadGlobalProperty($property);
180 foreach($document->xpath('//typeHandler') as $handler)
181 $this->loadTypeHandler($handler);
183 foreach($document->xpath('//connection[last()]') as $conn)
184 $this->loadDatabaseConnection($conn);
186 //try to load configuration in the current config file.
187 $mapping = new TSqlMapXmlMappingConfiguration($this);
188 $mapping->configure($filename);
190 foreach($document->xpath('//sqlMap') as $sqlmap)
191 $this->loadSqlMappingFiles($sqlmap);
193 $this->resolveResultMapping();
194 $this->attachCacheModels();
198 * Load global replacement property.
199 * @param SimpleXmlElement property node.
201 protected function loadGlobalProperty($node)
203 $this->_properties[(string)$node['name']] = (string)$node['value'];
207 * Load the type handler configurations.
208 * @param SimpleXmlElement type handler node
210 protected function loadTypeHandler($node)
212 $handler = $this->createObjectFromNode($node);
213 $this->_manager->getTypeHandlers()->registerTypeHandler($handler);
217 * Load the database connection tag.
218 * @param SimpleXmlElement connection node.
220 protected function loadDatabaseConnection($node)
222 $conn = $this->createObjectFromNode($node);
223 $this->_manager->setDbConnection($conn);
227 * Load SqlMap mapping configuration.
228 * @param unknown_type $node
230 protected function loadSqlMappingFiles($node)
232 if(strlen($resource = (string)$node['resource']) > 0)
234 if( strpos($resource, '${') !== false)
235 $resource = $this->replaceProperties($resource);
237 $mapping = new TSqlMapXmlMappingConfiguration($this);
238 $filename = $this->getAbsoluteFilePath($this->_configFile, $resource);
239 $mapping->configure($filename);
244 * Resolve nest result mappings.
246 protected function resolveResultMapping()
248 $maps = $this->_manager->getResultMaps();
249 foreach($maps as $entry)
251 foreach($entry->getColumns() as $item)
253 $resultMap = $item->getResultMapping();
254 if(strlen($resultMap) > 0)
256 if($maps->contains($resultMap))
257 $item->setNestedResultMap($maps[$resultMap]);
259 throw new TSqlMapConfigurationException(
260 'sqlmap_unable_to_find_result_mapping',
261 $resultMap, $this->_configFile, $entry->getID());
264 if($entry->getDiscriminator()!==null)
265 $entry->getDiscriminator()->initialize($this->_manager);
270 * Set the cache for each statement having a cache model property.
272 protected function attachCacheModels()
274 foreach($this->_manager->getMappedStatements() as $mappedStatement)
276 if(strlen($model = $mappedStatement->getStatement()->getCacheModel()) > 0)
278 $cache = $this->_manager->getCacheModel($model);
279 $mappedStatement->getStatement()->setCache($cache);
285 * Replace the place holders ${name} in text with properties the
286 * corresponding global property value.
287 * @param string original string.
288 * @return string string with global property replacement.
290 public function replaceProperties($string)
292 foreach($this->_properties as $find => $replace)
293 $string = str_replace('${'.$find.'}', $replace, $string);
299 * Loads the statements, result maps, parameters maps from xml configuration.
303 * @author Wei Zhuo <weizho[at]gmail[dot]com>
304 * @version $Id: TSqlMapXmlConfiguration.php 3245 2013-01-07 20:23:32Z ctrlaltca $
305 * @package System.Data.SqlMap.Configuration
308 class TSqlMapXmlMappingConfiguration extends TSqlMapXmlConfigBuilder
311 private $_configFile;
316 private $_FlushOnExecuteStatements=array();
319 * Regular expressions for escaping simple/inline parameter symbols
321 const SIMPLE_MARK='$';
322 const INLINE_SYMBOL='#';
323 const ESCAPED_SIMPLE_MARK_REGEXP='/\$\$/';
324 const ESCAPED_INLINE_SYMBOL_REGEXP='/\#\#/';
325 const SIMPLE_PLACEHOLDER='`!!`';
326 const INLINE_PLACEHOLDER='`!!!`';
329 * @param TSqlMapXmlConfiguration parent xml configuration.
331 public function __construct(TSqlMapXmlConfiguration $xmlConfig)
333 $this->_xmlConfig=$xmlConfig;
334 $this->_manager=$xmlConfig->getManager();
337 protected function getConfigFile()
339 return $this->_configFile;
343 * Configure an XML mapping.
344 * @param string xml mapping filename.
346 public function configure($filename)
348 $this->_configFile=$filename;
349 $document = $this->loadXmlDocument($filename,$this->_xmlConfig);
350 $this->_document=$document;
352 static $bCacheDependencies;
353 if($bCacheDependencies === null)
354 $bCacheDependencies = Prado::getApplication()->getMode() !== TApplicationMode::Performance;
356 if($bCacheDependencies)
357 $this->_manager->getCacheDependencies()
359 ->add(new TFileCacheDependency($filename));
361 foreach($document->xpath('//resultMap') as $node)
362 $this->loadResultMap($node);
364 foreach($document->xpath('//parameterMap') as $node)
365 $this->loadParameterMap($node);
367 foreach($document->xpath('//statement') as $node)
368 $this->loadStatementTag($node);
370 foreach($document->xpath('//select') as $node)
371 $this->loadSelectTag($node);
373 foreach($document->xpath('//insert') as $node)
374 $this->loadInsertTag($node);
376 foreach($document->xpath('//update') as $node)
377 $this->loadUpdateTag($node);
379 foreach($document->xpath('//delete') as $node)
380 $this->loadDeleteTag($node);
382 foreach($document->xpath('//procedure') as $node)
383 $this->loadProcedureTag($node);
385 foreach($document->xpath('//cacheModel') as $node)
386 $this->loadCacheModel($node);
388 $this->registerCacheTriggers();
392 * Load the result maps.
393 * @param SimpleXmlElement result map node.
395 protected function loadResultMap($node)
397 $resultMap = $this->createResultMap($node);
399 //find extended result map.
400 if(strlen($extendMap = $resultMap->getExtends()) > 0)
402 if(!$this->_manager->getResultMaps()->contains($extendMap))
404 $extendNode=$this->getElementByIdValue($this->_document,'resultMap',$extendMap);
405 if($extendNode!==null)
406 $this->loadResultMap($extendNode);
409 if(!$this->_manager->getResultMaps()->contains($extendMap))
410 throw new TSqlMapConfigurationException(
411 'sqlmap_unable_to_find_parent_result_map', $node, $this->_configFile, $extendMap);
413 $superMap = $this->_manager->getResultMap($extendMap);
414 $resultMap->getColumns()->mergeWith($superMap->getColumns());
418 if(!$this->_manager->getResultMaps()->contains($resultMap->getID()))
419 $this->_manager->addResultMap($resultMap);
423 * Create a new result map and its associated result properties,
424 * disciminiator and sub maps.
425 * @param SimpleXmlElement result map node
426 * @return TResultMap SqlMap result mapping.
428 protected function createResultMap($node)
430 $resultMap = new TResultMap();
431 $this->setObjectPropFromNode($resultMap,$node);
434 foreach($node->result as $result)
436 $property = new TResultProperty($resultMap);
437 $this->setObjectPropFromNode($property,$result);
438 $resultMap->addResultProperty($property);
441 //create the discriminator
442 $discriminator = null;
443 if(isset($node->discriminator))
445 $discriminator = new TDiscriminator();
446 $this->setObjectPropFromNode($discriminator, $node->discriminator);
447 $discriminator->initMapping($resultMap);
450 foreach($node->xpath('subMap') as $subMapNode)
452 if($discriminator===null)
453 throw new TSqlMapConfigurationException(
454 'sqlmap_undefined_discriminator', $node, $this->_configFile,$subMapNode);
455 $subMap = new TSubMap;
456 $this->setObjectPropFromNode($subMap,$subMapNode);
457 $discriminator->addSubMap($subMap);
460 if($discriminator!==null)
461 $resultMap->setDiscriminator($discriminator);
467 * Load parameter map from xml.
469 * @param SimpleXmlElement parameter map node.
471 protected function loadParameterMap($node)
473 $parameterMap = $this->createParameterMap($node);
475 if(strlen($extendMap = $parameterMap->getExtends()) > 0)
477 if(!$this->_manager->getParameterMaps()->contains($extendMap))
479 $extendNode=$this->getElementByIdValue($this->_document,'parameterMap',$extendMap);
480 if($extendNode!==null)
481 $this->loadParameterMap($extendNode);
484 if(!$this->_manager->getParameterMaps()->contains($extendMap))
485 throw new TSqlMapConfigurationException(
486 'sqlmap_unable_to_find_parent_parameter_map', $node, $this->_configFile,$extendMap);
487 $superMap = $this->_manager->getParameterMap($extendMap);
489 foreach($superMap->getPropertyNames() as $propertyName)
490 $parameterMap->insertProperty($index++,$superMap->getProperty($propertyName));
492 $this->_manager->addParameterMap($parameterMap);
496 * Create a new parameter map from xml node.
497 * @param SimpleXmlElement parameter map node.
498 * @return TParameterMap new parameter mapping.
500 protected function createParameterMap($node)
502 $parameterMap = new TParameterMap();
503 $this->setObjectPropFromNode($parameterMap,$node);
504 foreach($node->parameter as $parameter)
506 $property = new TParameterProperty();
507 $this->setObjectPropFromNode($property,$parameter);
508 $parameterMap->addProperty($property);
510 return $parameterMap;
514 * Load statement mapping from xml configuration file.
515 * @param SimpleXmlElement statement node.
517 protected function loadStatementTag($node)
519 $statement = new TSqlMapStatement();
520 $this->setObjectPropFromNode($statement,$node);
521 $this->processSqlStatement($statement, $node);
522 $mappedStatement = new TMappedStatement($this->_manager, $statement);
523 $this->_manager->addMappedStatement($mappedStatement);
527 * Load extended SQL statements if application. Replaces global properties
528 * in the sql text. Extracts inline parameter maps.
529 * @param TSqlMapStatement mapped statement.
530 * @param SimpleXmlElement statement node.
532 protected function processSqlStatement($statement, $node)
534 $commandText = (string)$node;
535 if(strlen($extend = $statement->getExtends()) > 0)
537 $superNode = $this->getElementByIdValue($this->_document,'*',$extend);
538 if($superNode!==null)
539 $commandText = (string)$superNode . $commandText;
541 throw new TSqlMapConfigurationException(
542 'sqlmap_unable_to_find_parent_sql', $extend, $this->_configFile,$node);
544 //$commandText = $this->_xmlConfig->replaceProperties($commandText);
545 $statement->initialize($this->_manager);
546 $this->applyInlineParameterMap($statement, $commandText, $node);
550 * Extract inline parameter maps.
551 * @param TSqlMapStatement statement object.
552 * @param string sql text
553 * @param SimpleXmlElement statement node.
555 protected function applyInlineParameterMap($statement, $sqlStatement, $node)
557 $scope['file'] = $this->_configFile;
558 $scope['node'] = $node;
560 $sqlStatement=preg_replace(self::ESCAPED_INLINE_SYMBOL_REGEXP,self::INLINE_PLACEHOLDER,$sqlStatement);
561 if($statement->parameterMap() === null)
563 // Build a Parametermap with the inline parameters.
564 // if they exist. Then delete inline infos from sqltext.
565 $parameterParser = new TInlineParameterMapParser;
566 $sqlText = $parameterParser->parse($sqlStatement, $scope);
567 if(count($sqlText['parameters']) > 0)
569 $map = new TParameterMap();
570 $map->setID($statement->getID().'-InLineParameterMap');
571 $statement->setInlineParameterMap($map);
572 foreach($sqlText['parameters'] as $property)
573 $map->addProperty($property);
575 $sqlStatement = $sqlText['sql'];
577 $sqlStatement=preg_replace('/'.self::INLINE_PLACEHOLDER.'/',self::INLINE_SYMBOL,$sqlStatement);
579 $this->prepareSql($statement, $sqlStatement, $node);
583 * Prepare the sql text (may extend to dynamic sql).
584 * @param TSqlMapStatement mapped statement.
585 * @param string sql text.
586 * @param SimpleXmlElement statement node.
587 * @todo Extend to dynamic sql.
589 protected function prepareSql($statement,$sqlStatement, $node)
591 $simpleDynamic = new TSimpleDynamicParser;
592 $sqlStatement=preg_replace(self::ESCAPED_SIMPLE_MARK_REGEXP,self::SIMPLE_PLACEHOLDER,$sqlStatement);
593 $dynamics = $simpleDynamic->parse($sqlStatement);
594 if(count($dynamics['parameters']) > 0)
596 $sql = new TSimpleDynamicSql($dynamics['parameters']);
597 $sqlStatement = $dynamics['sql'];
600 $sql = new TStaticSql();
601 $sqlStatement=preg_replace('/'.self::SIMPLE_PLACEHOLDER.'/',self::SIMPLE_MARK,$sqlStatement);
602 $sql->buildPreparedStatement($statement, $sqlStatement);
603 $statement->setSqlText($sql);
607 * Load select statement from xml mapping.
608 * @param SimpleXmlElement select node.
610 protected function loadSelectTag($node)
612 $select = new TSqlMapSelect;
613 $this->setObjectPropFromNode($select,$node);
614 $this->processSqlStatement($select,$node);
615 $mappedStatement = new TMappedStatement($this->_manager, $select);
616 if(strlen($select->getCacheModel()) > 0)
617 $mappedStatement = new TCachingStatement($mappedStatement);
619 $this->_manager->addMappedStatement($mappedStatement);
623 * Load insert statement from xml mapping.
624 * @param SimpleXmlElement insert node.
626 protected function loadInsertTag($node)
628 $insert = $this->createInsertStatement($node);
629 $this->processSqlStatement($insert, $node);
630 $mappedStatement = new TInsertMappedStatement($this->_manager, $insert);
631 $this->_manager->addMappedStatement($mappedStatement);
635 * Create new insert statement from xml node.
636 * @param SimpleXmlElement insert node.
637 * @return TSqlMapInsert insert statement.
639 protected function createInsertStatement($node)
641 $insert = new TSqlMapInsert;
642 $this->setObjectPropFromNode($insert,$node);
643 if(isset($node->selectKey))
644 $this->loadSelectKeyTag($insert,$node->selectKey);
649 * Load the selectKey statement from xml mapping.
650 * @param SimpleXmlElement selectkey node
652 protected function loadSelectKeyTag($insert, $node)
654 $selectKey = new TSqlMapSelectKey;
655 $this->setObjectPropFromNode($selectKey,$node);
656 $selectKey->setID($insert->getID());
657 $selectKey->setID($insert->getID().'.SelectKey');
658 $this->processSqlStatement($selectKey,$node);
659 $insert->setSelectKey($selectKey);
660 $mappedStatement = new TMappedStatement($this->_manager, $selectKey);
661 $this->_manager->addMappedStatement($mappedStatement);
665 * Load update statement from xml mapping.
666 * @param SimpleXmlElement update node.
668 protected function loadUpdateTag($node)
670 $update = new TSqlMapUpdate;
671 $this->setObjectPropFromNode($update,$node);
672 $this->processSqlStatement($update, $node);
673 $mappedStatement = new TUpdateMappedStatement($this->_manager, $update);
674 $this->_manager->addMappedStatement($mappedStatement);
678 * Load delete statement from xml mapping.
679 * @param SimpleXmlElement delete node.
681 protected function loadDeleteTag($node)
683 $delete = new TSqlMapDelete;
684 $this->setObjectPropFromNode($delete,$node);
685 $this->processSqlStatement($delete, $node);
686 $mappedStatement = new TDeleteMappedStatement($this->_manager, $delete);
687 $this->_manager->addMappedStatement($mappedStatement);
691 * Load procedure statement from xml mapping.
692 * @todo Implement loading procedure
693 * @param SimpleXmlElement procedure node
695 protected function loadProcedureTag($node)
697 //var_dump('todo: add load procedure');
701 * Load cache models from xml mapping.
702 * @param SimpleXmlElement cache node.
704 protected function loadCacheModel($node)
706 $cacheModel = new TSqlMapCacheModel;
707 $properties = array('id','implementation');
708 foreach($node->attributes() as $name=>$value)
710 if(in_array(strtolower($name), $properties))
711 $cacheModel->{'set'.$name}((string)$value);
713 $cache = Prado::createComponent($cacheModel->getImplementationClass(), $cacheModel);
714 $this->setObjectPropFromNode($cache,$node,$properties);
716 foreach($node->xpath('property') as $propertyNode)
718 $name = $propertyNode->attributes()->name;
719 if($name===null || $name==='') continue;
721 $value = $propertyNode->attributes()->value;
722 if($value===null || $value==='') continue;
724 if( !TPropertyAccess::has($cache, $name) ) continue;
726 TPropertyAccess::set($cache, $name, $value);
729 $this->loadFlushInterval($cacheModel,$node);
731 $cacheModel->initialize($cache);
732 $this->_manager->addCacheModel($cacheModel);
733 foreach($node->xpath('flushOnExecute') as $flush)
734 $this->loadFlushOnCache($cacheModel,$node,$flush);
738 * Load the flush interval
739 * @param TSqlMapCacheModel cache model
740 * @param SimpleXmlElement cache node
742 protected function loadFlushInterval($cacheModel, $node)
744 $flushInterval = $node->xpath('flushInterval');
745 if($flushInterval === null || count($flushInterval) === 0) return;
747 foreach($flushInterval[0]->attributes() as $name=>$value)
749 switch(strToLower($name))
752 $duration += (integer)$value;
755 $duration += 60 * (integer)$value;
758 $duration += 3600 * (integer)$value;
761 $duration += 86400 * (integer)$value;
764 $duration = (integer)$value;
765 break 2; // switch, foreach
768 $cacheModel->setFlushInterval($duration);
772 * Load the flush on cache properties.
773 * @param TSqlMapCacheModel cache model
774 * @param SimpleXmlElement parent node.
775 * @param SimpleXmlElement flush node.
777 protected function loadFlushOnCache($cacheModel,$parent,$node)
779 $id = $cacheModel->getID();
780 if(!isset($this->_FlushOnExecuteStatements[$id]))
781 $this->_FlushOnExecuteStatements[$id] = array();
782 foreach($node->attributes() as $name=>$value)
784 if(strtolower($name)==='statement')
785 $this->_FlushOnExecuteStatements[$id][] = (string)$value;
790 * Attach CacheModel to statement and register trigger statements for cache models
792 protected function registerCacheTriggers()
794 foreach($this->_FlushOnExecuteStatements as $cacheID => $statementIDs)
796 $cacheModel = $this->_manager->getCacheModel($cacheID);
797 foreach($statementIDs as $statementID)
799 $statement = $this->_manager->getMappedStatement($statementID);
800 $cacheModel->registerTriggerStatement($statement);