3 * TSqlMapXmlConfigBuilder, TSqlMapXmlConfiguration, TSqlMapXmlMappingConfiguration classes file.
5 * @author Wei Zhuo <weizhuo[at]gmail[dot]com>
6 * @link https://github.com/pradosoft/prado
7 * @copyright Copyright © 2005-2016 The PRADO Group
8 * @license https://github.com/pradosoft/prado/blob/master/COPYRIGHT
9 * @package System.Data.SqlMap.Configuration
12 Prado::using('System.Data.SqlMap.Configuration.TSqlMapStatement');
15 * TSqlMapXmlConfig class file.
17 * @author Wei Zhuo <weizhuo[at]gmail[dot]com>
18 * @package System.Data.SqlMap.Configuration
20 abstract class TSqlMapXmlConfigBuilder
23 * Create an instance of an object give by the attribute named 'class' in the
24 * node and set the properties on the object given by attribute names and values.
25 * @param SimpleXmlNode property node
26 * @return Object new instance of class with class name given by 'class' attribute value.
28 protected function createObjectFromNode($node)
30 if(isset($node['class']))
32 $obj = Prado::createComponent((string)$node['class']);
33 $this->setObjectPropFromNode($obj,$node,array('class'));
36 throw new TSqlMapConfigurationException(
37 'sqlmap_node_class_undef', $node, $this->getConfigFile());
41 * For each attributes (excluding attribute named in $except) set the
42 * property of the $obj given by the name of the attribute with the value
44 * @param Object object instance
45 * @param SimpleXmlNode property node
46 * @param array exception property name
48 protected function setObjectPropFromNode($obj,$node,$except=array())
50 foreach($node->attributes() as $name=>$value)
52 if(!in_array($name,$except))
54 if($obj->canSetProperty($name))
55 $obj->{$name} = (string)$value;
57 throw new TSqlMapConfigurationException(
58 'sqlmap_invalid_property', $name, get_class($obj),
59 $node, $this->getConfigFile());
65 * Gets the filename relative to the basefile.
66 * @param string base filename
67 * @param string relative filename
68 * @return string absolute filename.
70 protected function getAbsoluteFilePath($basefile,$resource)
72 $basedir = dirname($basefile);
73 $file = realpath($basedir.DIRECTORY_SEPARATOR.$resource);
74 if(!is_string($file) || !is_file($file))
75 $file = realpath($resource);
76 if(is_string($file) && is_file($file))
79 throw new TSqlMapConfigurationException(
80 'sqlmap_unable_to_find_resource', $resource);
84 * Load document using simple xml.
85 * @param string filename.
86 * @return SimpleXmlElement xml document.
88 protected function loadXmlDocument($filename,TSqlMapXmlConfiguration $config)
90 if( strpos($filename, '${') !== false)
91 $filename = $config->replaceProperties($filename);
93 if(!is_file($filename))
94 throw new TSqlMapConfigurationException(
95 'sqlmap_unable_to_find_config', $filename);
96 return simplexml_load_string($config->replaceProperties(file_get_contents($filename)));
100 * Get element node by ID value (try for attribute name ID as case insensitive).
101 * @param SimpleXmlDocument $document
102 * @param string tag name.
103 * @param string id value.
104 * @return SimpleXmlElement node if found, null otherwise.
106 protected function getElementByIdValue($document, $tag, $value)
108 //hack to allow upper case and lower case attribute names.
109 foreach(array('id','ID','Id', 'iD') as $id)
111 $xpath = "//{$tag}[@{$id}='{$value}']";
112 foreach($document->xpath($xpath) as $node)
118 * @return string configuration file.
120 protected abstract function getConfigFile();
124 * TSqlMapXmlConfig class.
126 * Configures the TSqlMapManager using xml configuration file.
128 * @author Wei Zhuo <weizho[at]gmail[dot]com>
129 * @package System.Data.SqlMap.Configuration
132 class TSqlMapXmlConfiguration extends TSqlMapXmlConfigBuilder
135 * @var TSqlMapManager manager
139 * @var string configuration file.
141 private $_configFile;
143 * @var array global properties.
145 private $_properties=array();
148 * @param TSqlMapManager manager instance.
150 public function __construct($manager)
152 $this->_manager=$manager;
155 public function getManager()
157 return $this->_manager;
160 protected function getConfigFile()
162 return $this->_configFile;
166 * Configure the TSqlMapManager using the given xml file.
167 * @param string SqlMap configuration xml file.
169 public function configure($filename=null)
171 $this->_configFile=$filename;
172 $document = $this->loadXmlDocument($filename,$this);
174 foreach($document->xpath('//property') as $property)
175 $this->loadGlobalProperty($property);
177 foreach($document->xpath('//typeHandler') as $handler)
178 $this->loadTypeHandler($handler);
180 foreach($document->xpath('//connection[last()]') as $conn)
181 $this->loadDatabaseConnection($conn);
183 //try to load configuration in the current config file.
184 $mapping = new TSqlMapXmlMappingConfiguration($this);
185 $mapping->configure($filename);
187 foreach($document->xpath('//sqlMap') as $sqlmap)
188 $this->loadSqlMappingFiles($sqlmap);
190 $this->resolveResultMapping();
191 $this->attachCacheModels();
195 * Load global replacement property.
196 * @param SimpleXmlElement property node.
198 protected function loadGlobalProperty($node)
200 $this->_properties[(string)$node['name']] = (string)$node['value'];
204 * Load the type handler configurations.
205 * @param SimpleXmlElement type handler node
207 protected function loadTypeHandler($node)
209 $handler = $this->createObjectFromNode($node);
210 $this->_manager->getTypeHandlers()->registerTypeHandler($handler);
214 * Load the database connection tag.
215 * @param SimpleXmlElement connection node.
217 protected function loadDatabaseConnection($node)
219 $conn = $this->createObjectFromNode($node);
220 $this->_manager->setDbConnection($conn);
224 * Load SqlMap mapping configuration.
225 * @param unknown_type $node
227 protected function loadSqlMappingFiles($node)
229 if(strlen($resource = (string)$node['resource']) > 0)
231 if( strpos($resource, '${') !== false)
232 $resource = $this->replaceProperties($resource);
234 $mapping = new TSqlMapXmlMappingConfiguration($this);
235 $filename = $this->getAbsoluteFilePath($this->_configFile, $resource);
236 $mapping->configure($filename);
241 * Resolve nest result mappings.
243 protected function resolveResultMapping()
245 $maps = $this->_manager->getResultMaps();
246 foreach($maps as $entry)
248 foreach($entry->getColumns() as $item)
250 $resultMap = $item->getResultMapping();
251 if(strlen($resultMap) > 0)
253 if($maps->contains($resultMap))
254 $item->setNestedResultMap($maps[$resultMap]);
256 throw new TSqlMapConfigurationException(
257 'sqlmap_unable_to_find_result_mapping',
258 $resultMap, $this->_configFile, $entry->getID());
261 if($entry->getDiscriminator()!==null)
262 $entry->getDiscriminator()->initialize($this->_manager);
267 * Set the cache for each statement having a cache model property.
269 protected function attachCacheModels()
271 foreach($this->_manager->getMappedStatements() as $mappedStatement)
273 if(strlen($model = $mappedStatement->getStatement()->getCacheModel()) > 0)
275 $cache = $this->_manager->getCacheModel($model);
276 $mappedStatement->getStatement()->setCache($cache);
282 * Replace the place holders ${name} in text with properties the
283 * corresponding global property value.
284 * @param string original string.
285 * @return string string with global property replacement.
287 public function replaceProperties($string)
289 foreach($this->_properties as $find => $replace)
290 $string = str_replace('${'.$find.'}', $replace, $string);
296 * Loads the statements, result maps, parameters maps from xml configuration.
300 * @author Wei Zhuo <weizho[at]gmail[dot]com>
301 * @package System.Data.SqlMap.Configuration
304 class TSqlMapXmlMappingConfiguration extends TSqlMapXmlConfigBuilder
307 private $_configFile;
312 private $_FlushOnExecuteStatements=array();
315 * Regular expressions for escaping simple/inline parameter symbols
317 const SIMPLE_MARK='$';
318 const INLINE_SYMBOL='#';
319 const ESCAPED_SIMPLE_MARK_REGEXP='/\$\$/';
320 const ESCAPED_INLINE_SYMBOL_REGEXP='/\#\#/';
321 const SIMPLE_PLACEHOLDER='`!!`';
322 const INLINE_PLACEHOLDER='`!!!`';
325 * @param TSqlMapXmlConfiguration parent xml configuration.
327 public function __construct(TSqlMapXmlConfiguration $xmlConfig)
329 $this->_xmlConfig=$xmlConfig;
330 $this->_manager=$xmlConfig->getManager();
333 protected function getConfigFile()
335 return $this->_configFile;
339 * Configure an XML mapping.
340 * @param string xml mapping filename.
342 public function configure($filename)
344 $this->_configFile=$filename;
345 $document = $this->loadXmlDocument($filename,$this->_xmlConfig);
346 $this->_document=$document;
348 static $bCacheDependencies;
349 if($bCacheDependencies === null)
350 $bCacheDependencies = true; //Prado::getApplication()->getMode() !== TApplicationMode::Performance;
352 if($bCacheDependencies)
353 $this->_manager->getCacheDependencies()
355 ->add(new TFileCacheDependency($filename));
357 foreach($document->xpath('//resultMap') as $node)
358 $this->loadResultMap($node);
360 foreach($document->xpath('//parameterMap') as $node)
361 $this->loadParameterMap($node);
363 foreach($document->xpath('//statement') as $node)
364 $this->loadStatementTag($node);
366 foreach($document->xpath('//select') as $node)
367 $this->loadSelectTag($node);
369 foreach($document->xpath('//insert') as $node)
370 $this->loadInsertTag($node);
372 foreach($document->xpath('//update') as $node)
373 $this->loadUpdateTag($node);
375 foreach($document->xpath('//delete') as $node)
376 $this->loadDeleteTag($node);
378 foreach($document->xpath('//procedure') as $node)
379 $this->loadProcedureTag($node);
381 foreach($document->xpath('//cacheModel') as $node)
382 $this->loadCacheModel($node);
384 $this->registerCacheTriggers();
388 * Load the result maps.
389 * @param SimpleXmlElement result map node.
391 protected function loadResultMap($node)
393 $resultMap = $this->createResultMap($node);
395 //find extended result map.
396 if(strlen($extendMap = $resultMap->getExtends()) > 0)
398 if(!$this->_manager->getResultMaps()->contains($extendMap))
400 $extendNode=$this->getElementByIdValue($this->_document,'resultMap',$extendMap);
401 if($extendNode!==null)
402 $this->loadResultMap($extendNode);
405 if(!$this->_manager->getResultMaps()->contains($extendMap))
406 throw new TSqlMapConfigurationException(
407 'sqlmap_unable_to_find_parent_result_map', $node, $this->_configFile, $extendMap);
409 $superMap = $this->_manager->getResultMap($extendMap);
410 $resultMap->getColumns()->mergeWith($superMap->getColumns());
414 if(!$this->_manager->getResultMaps()->contains($resultMap->getID()))
415 $this->_manager->addResultMap($resultMap);
419 * Create a new result map and its associated result properties,
420 * disciminiator and sub maps.
421 * @param SimpleXmlElement result map node
422 * @return TResultMap SqlMap result mapping.
424 protected function createResultMap($node)
426 $resultMap = new TResultMap();
427 $this->setObjectPropFromNode($resultMap,$node);
430 foreach($node->result as $result)
432 $property = new TResultProperty($resultMap);
433 $this->setObjectPropFromNode($property,$result);
434 $resultMap->addResultProperty($property);
437 //create the discriminator
438 $discriminator = null;
439 if(isset($node->discriminator))
441 $discriminator = new TDiscriminator();
442 $this->setObjectPropFromNode($discriminator, $node->discriminator);
443 $discriminator->initMapping($resultMap);
446 foreach($node->xpath('subMap') as $subMapNode)
448 if($discriminator===null)
449 throw new TSqlMapConfigurationException(
450 'sqlmap_undefined_discriminator', $node, $this->_configFile,$subMapNode);
451 $subMap = new TSubMap;
452 $this->setObjectPropFromNode($subMap,$subMapNode);
453 $discriminator->addSubMap($subMap);
456 if($discriminator!==null)
457 $resultMap->setDiscriminator($discriminator);
463 * Load parameter map from xml.
465 * @param SimpleXmlElement parameter map node.
467 protected function loadParameterMap($node)
469 $parameterMap = $this->createParameterMap($node);
471 if(strlen($extendMap = $parameterMap->getExtends()) > 0)
473 if(!$this->_manager->getParameterMaps()->contains($extendMap))
475 $extendNode=$this->getElementByIdValue($this->_document,'parameterMap',$extendMap);
476 if($extendNode!==null)
477 $this->loadParameterMap($extendNode);
480 if(!$this->_manager->getParameterMaps()->contains($extendMap))
481 throw new TSqlMapConfigurationException(
482 'sqlmap_unable_to_find_parent_parameter_map', $node, $this->_configFile,$extendMap);
483 $superMap = $this->_manager->getParameterMap($extendMap);
485 foreach($superMap->getPropertyNames() as $propertyName)
486 $parameterMap->insertProperty($index++,$superMap->getProperty($propertyName));
488 $this->_manager->addParameterMap($parameterMap);
492 * Create a new parameter map from xml node.
493 * @param SimpleXmlElement parameter map node.
494 * @return TParameterMap new parameter mapping.
496 protected function createParameterMap($node)
498 $parameterMap = new TParameterMap();
499 $this->setObjectPropFromNode($parameterMap,$node);
500 foreach($node->parameter as $parameter)
502 $property = new TParameterProperty();
503 $this->setObjectPropFromNode($property,$parameter);
504 $parameterMap->addProperty($property);
506 return $parameterMap;
510 * Load statement mapping from xml configuration file.
511 * @param SimpleXmlElement statement node.
513 protected function loadStatementTag($node)
515 $statement = new TSqlMapStatement();
516 $this->setObjectPropFromNode($statement,$node);
517 $this->processSqlStatement($statement, $node);
518 $mappedStatement = new TMappedStatement($this->_manager, $statement);
519 $this->_manager->addMappedStatement($mappedStatement);
523 * Load extended SQL statements if application. Replaces global properties
524 * in the sql text. Extracts inline parameter maps.
525 * @param TSqlMapStatement mapped statement.
526 * @param SimpleXmlElement statement node.
528 protected function processSqlStatement($statement, $node)
530 $commandText = (string)$node;
531 if(strlen($extend = $statement->getExtends()) > 0)
533 $superNode = $this->getElementByIdValue($this->_document,'*',$extend);
534 if($superNode!==null)
535 $commandText = (string)$superNode . $commandText;
537 throw new TSqlMapConfigurationException(
538 'sqlmap_unable_to_find_parent_sql', $extend, $this->_configFile,$node);
540 //$commandText = $this->_xmlConfig->replaceProperties($commandText);
541 $statement->initialize($this->_manager);
542 $this->applyInlineParameterMap($statement, $commandText, $node);
546 * Extract inline parameter maps.
547 * @param TSqlMapStatement statement object.
548 * @param string sql text
549 * @param SimpleXmlElement statement node.
551 protected function applyInlineParameterMap($statement, $sqlStatement, $node)
553 $scope['file'] = $this->_configFile;
554 $scope['node'] = $node;
556 $sqlStatement=preg_replace(self::ESCAPED_INLINE_SYMBOL_REGEXP,self::INLINE_PLACEHOLDER,$sqlStatement);
557 if($statement->parameterMap() === null)
559 // Build a Parametermap with the inline parameters.
560 // if they exist. Then delete inline infos from sqltext.
561 $parameterParser = new TInlineParameterMapParser;
562 $sqlText = $parameterParser->parse($sqlStatement, $scope);
563 if(count($sqlText['parameters']) > 0)
565 $map = new TParameterMap();
566 $map->setID($statement->getID().'-InLineParameterMap');
567 $statement->setInlineParameterMap($map);
568 foreach($sqlText['parameters'] as $property)
569 $map->addProperty($property);
571 $sqlStatement = $sqlText['sql'];
573 $sqlStatement=preg_replace('/'.self::INLINE_PLACEHOLDER.'/',self::INLINE_SYMBOL,$sqlStatement);
575 $this->prepareSql($statement, $sqlStatement, $node);
579 * Prepare the sql text (may extend to dynamic sql).
580 * @param TSqlMapStatement mapped statement.
581 * @param string sql text.
582 * @param SimpleXmlElement statement node.
583 * @todo Extend to dynamic sql.
585 protected function prepareSql($statement,$sqlStatement, $node)
587 $simpleDynamic = new TSimpleDynamicParser;
588 $sqlStatement=preg_replace(self::ESCAPED_SIMPLE_MARK_REGEXP,self::SIMPLE_PLACEHOLDER,$sqlStatement);
589 $dynamics = $simpleDynamic->parse($sqlStatement);
590 if(count($dynamics['parameters']) > 0)
592 $sql = new TSimpleDynamicSql($dynamics['parameters']);
593 $sqlStatement = $dynamics['sql'];
596 $sql = new TStaticSql();
597 $sqlStatement=preg_replace('/'.self::SIMPLE_PLACEHOLDER.'/',self::SIMPLE_MARK,$sqlStatement);
598 $sql->buildPreparedStatement($statement, $sqlStatement);
599 $statement->setSqlText($sql);
603 * Load select statement from xml mapping.
604 * @param SimpleXmlElement select node.
606 protected function loadSelectTag($node)
608 $select = new TSqlMapSelect;
609 $this->setObjectPropFromNode($select,$node);
610 $this->processSqlStatement($select,$node);
611 $mappedStatement = new TMappedStatement($this->_manager, $select);
612 if(strlen($select->getCacheModel()) > 0)
613 $mappedStatement = new TCachingStatement($mappedStatement);
615 $this->_manager->addMappedStatement($mappedStatement);
619 * Load insert statement from xml mapping.
620 * @param SimpleXmlElement insert node.
622 protected function loadInsertTag($node)
624 $insert = $this->createInsertStatement($node);
625 $this->processSqlStatement($insert, $node);
626 $mappedStatement = new TInsertMappedStatement($this->_manager, $insert);
627 $this->_manager->addMappedStatement($mappedStatement);
631 * Create new insert statement from xml node.
632 * @param SimpleXmlElement insert node.
633 * @return TSqlMapInsert insert statement.
635 protected function createInsertStatement($node)
637 $insert = new TSqlMapInsert;
638 $this->setObjectPropFromNode($insert,$node);
639 if(isset($node->selectKey))
640 $this->loadSelectKeyTag($insert,$node->selectKey);
645 * Load the selectKey statement from xml mapping.
646 * @param SimpleXmlElement selectkey node
648 protected function loadSelectKeyTag($insert, $node)
650 $selectKey = new TSqlMapSelectKey;
651 $this->setObjectPropFromNode($selectKey,$node);
652 $selectKey->setID($insert->getID());
653 $selectKey->setID($insert->getID().'.SelectKey');
654 $this->processSqlStatement($selectKey,$node);
655 $insert->setSelectKey($selectKey);
656 $mappedStatement = new TMappedStatement($this->_manager, $selectKey);
657 $this->_manager->addMappedStatement($mappedStatement);
661 * Load update statement from xml mapping.
662 * @param SimpleXmlElement update node.
664 protected function loadUpdateTag($node)
666 $update = new TSqlMapUpdate;
667 $this->setObjectPropFromNode($update,$node);
668 $this->processSqlStatement($update, $node);
669 $mappedStatement = new TUpdateMappedStatement($this->_manager, $update);
670 $this->_manager->addMappedStatement($mappedStatement);
674 * Load delete statement from xml mapping.
675 * @param SimpleXmlElement delete node.
677 protected function loadDeleteTag($node)
679 $delete = new TSqlMapDelete;
680 $this->setObjectPropFromNode($delete,$node);
681 $this->processSqlStatement($delete, $node);
682 $mappedStatement = new TDeleteMappedStatement($this->_manager, $delete);
683 $this->_manager->addMappedStatement($mappedStatement);
687 * Load procedure statement from xml mapping.
688 * @todo Implement loading procedure
689 * @param SimpleXmlElement procedure node
691 protected function loadProcedureTag($node)
693 //var_dump('todo: add load procedure');
697 * Load cache models from xml mapping.
698 * @param SimpleXmlElement cache node.
700 protected function loadCacheModel($node)
702 $cacheModel = new TSqlMapCacheModel;
703 $properties = array('id','implementation');
704 foreach($node->attributes() as $name=>$value)
706 if(in_array(strtolower($name), $properties))
707 $cacheModel->{'set'.$name}((string)$value);
709 $cache = Prado::createComponent($cacheModel->getImplementationClass(), $cacheModel);
710 $this->setObjectPropFromNode($cache,$node,$properties);
712 foreach($node->xpath('property') as $propertyNode)
714 $name = $propertyNode->attributes()->name;
715 if($name===null || $name==='') continue;
717 $value = $propertyNode->attributes()->value;
718 if($value===null || $value==='') continue;
720 if( !TPropertyAccess::has($cache, $name) ) continue;
722 TPropertyAccess::set($cache, $name, $value);
725 $this->loadFlushInterval($cacheModel,$node);
727 $cacheModel->initialize($cache);
728 $this->_manager->addCacheModel($cacheModel);
729 foreach($node->xpath('flushOnExecute') as $flush)
730 $this->loadFlushOnCache($cacheModel,$node,$flush);
734 * Load the flush interval
735 * @param TSqlMapCacheModel cache model
736 * @param SimpleXmlElement cache node
738 protected function loadFlushInterval($cacheModel, $node)
740 $flushInterval = $node->xpath('flushInterval');
741 if($flushInterval === null || count($flushInterval) === 0) return;
743 foreach($flushInterval[0]->attributes() as $name=>$value)
745 switch(strToLower($name))
748 $duration += (integer)$value;
751 $duration += 60 * (integer)$value;
754 $duration += 3600 * (integer)$value;
757 $duration += 86400 * (integer)$value;
760 $duration = (integer)$value;
761 break 2; // switch, foreach
764 $cacheModel->setFlushInterval($duration);
768 * Load the flush on cache properties.
769 * @param TSqlMapCacheModel cache model
770 * @param SimpleXmlElement parent node.
771 * @param SimpleXmlElement flush node.
773 protected function loadFlushOnCache($cacheModel,$parent,$node)
775 $id = $cacheModel->getID();
776 if(!isset($this->_FlushOnExecuteStatements[$id]))
777 $this->_FlushOnExecuteStatements[$id] = array();
778 foreach($node->attributes() as $name=>$value)
780 if(strtolower($name)==='statement')
781 $this->_FlushOnExecuteStatements[$id][] = (string)$value;
786 * Attach CacheModel to statement and register trigger statements for cache models
788 protected function registerCacheTriggers()
790 foreach($this->_FlushOnExecuteStatements as $cacheID => $statementIDs)
792 $cacheModel = $this->_manager->getCacheModel($cacheID);
793 foreach($statementIDs as $statementID)
795 $statement = $this->_manager->getMappedStatement($statementID);
796 $cacheModel->registerTriggerStatement($statement);