Passed
Push — master ( d943e6...a9e422 )
by Fabio
05:14
created

TSqlMapXmlMappingConfiguration::loadParameterMap()   B

Complexity

Conditions 7
Paths 7

Size

Total Lines 27
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 56

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 7
eloc 18
c 1
b 0
f 0
nc 7
nop 1
dl 0
loc 27
ccs 0
cts 18
cp 0
crap 56
rs 8.8333
1
<?php
2
/**
3
 * TSqlMapXmlConfigBuilder, TSqlMapXmlConfiguration, TSqlMapXmlMappingConfiguration classes file.
4
 *
5
 * @author Wei Zhuo <weizhuo[at]gmail[dot]com>
6
 * @link https://github.com/pradosoft/prado
7
 * @license https://github.com/pradosoft/prado/blob/master/LICENSE
8
 * @package Prado\Data\SqlMap\Configuration
9
 */
10
11
namespace Prado\Data\SqlMap\Configuration;
12
13
use Prado\Caching\TFileCacheDependency;
14
use Prado\Data\SqlMap\DataMapper\TPropertyAccess;
15
use Prado\Data\SqlMap\DataMapper\TSqlMapConfigurationException;
16
use Prado\Data\SqlMap\Statements\TCachingStatement;
17
use Prado\Data\SqlMap\Statements\TDeleteMappedStatement;
18
use Prado\Data\SqlMap\Statements\TInsertMappedStatement;
19
use Prado\Data\SqlMap\Statements\TMappedStatement;
20
use Prado\Data\SqlMap\Statements\TSimpleDynamicSql;
21
use Prado\Data\SqlMap\Statements\TStaticSql;
22
use Prado\Data\SqlMap\Statements\TUpdateMappedStatement;
23
use Prado\Prado;
24
25
/**
26
 * Loads the statements, result maps, parameters maps from xml configuration.
27
 *
28
 * description
29
 *
30
 * @author Wei Zhuo <weizho[at]gmail[dot]com>
31
 * @package Prado\Data\SqlMap\Configuration
32
 * @since 3.1
33
 */
34
class TSqlMapXmlMappingConfiguration extends TSqlMapXmlConfigBuilder
35
{
36
	private $_xmlConfig;
37
	private $_configFile;
38
	private $_manager;
39
40
	private $_document;
41
42
	private $_FlushOnExecuteStatements = [];
43
44
	/**
45
	 * Regular expressions for escaping simple/inline parameter symbols
46
	 */
47
	public const SIMPLE_MARK = '$';
48
	public const INLINE_SYMBOL = '#';
49
	public const ESCAPED_SIMPLE_MARK_REGEXP = '/\$\$/';
50
	public const ESCAPED_INLINE_SYMBOL_REGEXP = '/\#\#/';
51
	public const SIMPLE_PLACEHOLDER = '`!!`';
52
	public const INLINE_PLACEHOLDER = '`!!!`';
53
54
	/**
55
	 * @param TSqlMapXmlConfiguration $xmlConfig parent xml configuration.
56
	 */
57 3
	public function __construct(TSqlMapXmlConfiguration $xmlConfig)
58
	{
59 3
		$this->_xmlConfig = $xmlConfig;
60 3
		$this->_manager = $xmlConfig->getManager();
61 3
	}
62
63 1
	protected function getConfigFile()
64
	{
65 1
		return $this->_configFile;
66
	}
67
68
	/**
69
	 * Configure an XML mapping.
70
	 * @param string $filename xml mapping filename.
71
	 */
72 3
	public function configure($filename)
73
	{
74 3
		$this->_configFile = $filename;
75 3
		$document = $this->loadXmlDocument($filename, $this->_xmlConfig);
76 3
		$this->_document = $document;
77
78 3
		static $bCacheDependencies;
79 3
		if ($bCacheDependencies === null) {
80
			$bCacheDependencies = true;
81
		} //Prado::getApplication()->getMode() !== TApplicationMode::Performance;
82
83 3
		if ($bCacheDependencies) {
84 3
			$this->_manager->getCacheDependencies()
85 3
					->getDependencies()
86 3
					->add(new TFileCacheDependency($filename));
87
		}
88
89 3
		foreach ($document->xpath('//resultMap') as $node) {
90
			$this->loadResultMap($node);
91
		}
92
93 3
		foreach ($document->xpath('//parameterMap') as $node) {
94
			$this->loadParameterMap($node);
95
		}
96
97 3
		foreach ($document->xpath('//statement') as $node) {
98 2
			$this->loadStatementTag($node);
99
		}
100
101 2
		foreach ($document->xpath('//select') as $node) {
102 2
			$this->loadSelectTag($node);
103
		}
104
105 2
		foreach ($document->xpath('//insert') as $node) {
106
			$this->loadInsertTag($node);
107
		}
108
109 2
		foreach ($document->xpath('//update') as $node) {
110
			$this->loadUpdateTag($node);
111
		}
112
113 2
		foreach ($document->xpath('//delete') as $node) {
114
			$this->loadDeleteTag($node);
115
		}
116
117 2
		foreach ($document->xpath('//procedure') as $node) {
118
			$this->loadProcedureTag($node);
119
		}
120
121 2
		foreach ($document->xpath('//cacheModel') as $node) {
122
			$this->loadCacheModel($node);
123
		}
124
125 2
		$this->registerCacheTriggers();
126 2
	}
127
128
	/**
129
	 * Load the result maps.
130
	 * @param \SimpleXmlElement $node result map node.
131
	 */
132
	protected function loadResultMap($node)
133
	{
134
		$resultMap = $this->createResultMap($node);
135
136
		//find extended result map.
137
		$extendMap = $resultMap->getExtends();
138
		if ($extendMap !== null && strlen($extendMap) > 0) {
139
			if (!$this->_manager->getResultMaps()->contains($extendMap)) {
140
				$extendNode = $this->getElementByIdValue($this->_document, 'resultMap', $extendMap);
141
				if ($extendNode !== null) {
142
					$this->loadResultMap($extendNode);
143
				}
144
			}
145
146
			if (!$this->_manager->getResultMaps()->contains($extendMap)) {
147
				throw new TSqlMapConfigurationException(
148
					'sqlmap_unable_to_find_parent_result_map',
149
					$node,
150
					$this->_configFile,
151
					$extendMap
152
				);
153
			}
154
155
			$superMap = $this->_manager->getResultMap($extendMap);
156
			$resultMap->getColumns()->mergeWith($superMap->getColumns());
157
		}
158
159
		//add the result map
160
		if (!$this->_manager->getResultMaps()->contains($resultMap->getID())) {
161
			$this->_manager->addResultMap($resultMap);
162
		}
163
	}
164
165
	/**
166
	 * Create a new result map and its associated result properties,
167
	 * disciminiator and sub maps.
168
	 * @param \SimpleXmlElement $node result map node
169
	 * @return TResultMap SqlMap result mapping.
170
	 */
171
	protected function createResultMap($node)
172
	{
173
		$resultMap = new TResultMap();
174
		$this->setObjectPropFromNode($resultMap, $node);
175
176
		//result nodes
177
		foreach ($node->result as $result) {
178
			$property = new TResultProperty($resultMap);
179
			$this->setObjectPropFromNode($property, $result);
180
			$resultMap->addResultProperty($property);
181
		}
182
183
		//create the discriminator
184
		$discriminator = null;
185
		if (isset($node->discriminator)) {
186
			$discriminator = new TDiscriminator();
187
			$this->setObjectPropFromNode($discriminator, $node->discriminator);
188
			$discriminator->initMapping($resultMap);
189
		}
190
191
		foreach ($node->xpath('subMap') as $subMapNode) {
192
			if ($discriminator === null) {
193
				throw new TSqlMapConfigurationException(
194
					'sqlmap_undefined_discriminator',
195
					$node,
196
					$this->_configFile,
197
					$subMapNode
198
				);
199
			}
200
			$subMap = new TSubMap;
201
			$this->setObjectPropFromNode($subMap, $subMapNode);
202
			$discriminator->addSubMap($subMap);
203
		}
204
205
		if ($discriminator !== null) {
206
			$resultMap->setDiscriminator($discriminator);
207
		}
208
209
		return $resultMap;
210
	}
211
212
	/**
213
	 * Load parameter map from xml.
214
	 *
215
	 * @param \SimpleXmlElement $node parameter map node.
216
	 */
217
	protected function loadParameterMap($node)
218
	{
219
		$parameterMap = $this->createParameterMap($node);
220
		$extendMap = $parameterMap->getExtends();
221
		if ($extendMap !== null && strlen($extendMap) > 0) {
222
			if (!$this->_manager->getParameterMaps()->contains($extendMap)) {
223
				$extendNode = $this->getElementByIdValue($this->_document, 'parameterMap', $extendMap);
224
				if ($extendNode !== null) {
225
					$this->loadParameterMap($extendNode);
226
				}
227
			}
228
229
			if (!$this->_manager->getParameterMaps()->contains($extendMap)) {
230
				throw new TSqlMapConfigurationException(
231
					'sqlmap_unable_to_find_parent_parameter_map',
232
					$node,
233
					$this->_configFile,
234
					$extendMap
235
				);
236
			}
237
			$superMap = $this->_manager->getParameterMap($extendMap);
238
			$index = 0;
239
			foreach ($superMap->getPropertyNames() as $propertyName) {
240
				$parameterMap->insertProperty($index++, $superMap->getProperty($propertyName));
241
			}
242
		}
243
		$this->_manager->addParameterMap($parameterMap);
244
	}
245
246
	/**
247
	 * Create a new parameter map from xml node.
248
	 * @param \SimpleXmlElement $node parameter map node.
249
	 * @return TParameterMap new parameter mapping.
250
	 */
251
	protected function createParameterMap($node)
252
	{
253
		$parameterMap = new TParameterMap();
254
		$this->setObjectPropFromNode($parameterMap, $node);
255
		foreach ($node->parameter as $parameter) {
256
			$property = new TParameterProperty();
257
			$this->setObjectPropFromNode($property, $parameter);
258
			$parameterMap->addProperty($property);
259
		}
260
		return $parameterMap;
261
	}
262
263
	/**
264
	 * Load statement mapping from xml configuration file.
265
	 * @param \SimpleXmlElement $node statement node.
266 2
	 */
267
	protected function loadStatementTag($node)
268 2
	{
269 2
		$statement = new TSqlMapStatement();
270 1
		$this->setObjectPropFromNode($statement, $node);
271 1
		$this->processSqlStatement($statement, $node);
272 1
		$mappedStatement = new TMappedStatement($this->_manager, $statement);
273 1
		$this->_manager->addMappedStatement($mappedStatement);
274
	}
275
276
	/**
277
	 * Load extended SQL statements if application. Replaces global properties
278
	 * in the sql text. Extracts inline parameter maps.
279
	 * @param TSqlMapStatement $statement mapped statement.
280
	 * @param \SimpleXmlElement $node statement node.
281 2
	 */
282
	protected function processSqlStatement($statement, $node)
283 2
	{
284 2
		$commandText = (string) $node;
285 1
		$extend = $statement->getExtends();
286 1
		if ($extend !== null && strlen($extend) > 0) {
287 1
			$superNode = $this->getElementByIdValue($this->_document, '*', $extend);
288
			if ($superNode !== null) {
289
				$commandText = (string) $superNode . $commandText;
290
			} else {
291
				throw new TSqlMapConfigurationException(
292
					'sqlmap_unable_to_find_parent_sql',
293
					$extend,
294
					$this->_configFile,
295
					$node
296
				);
297
			}
298 2
		}
299 2
		//$commandText = $this->_xmlConfig->replaceProperties($commandText);
300 2
		$statement->initialize($this->_manager);
301
		$this->applyInlineParameterMap($statement, $commandText, $node);
302
	}
303
304
	/**
305
	 * Extract inline parameter maps.
306
	 * @param TSqlMapStatement $statement statement object.
307
	 * @param string $sqlStatement sql text
308 2
	 * @param \SimpleXmlElement $node statement node.
309
	 */
310 2
	protected function applyInlineParameterMap($statement, $sqlStatement, $node)
311 2
	{
312
		$scope['file'] = $this->_configFile;
0 ignored issues
show
Comprehensibility Best Practice introduced by
$scope was never initialized. Although not strictly required by PHP, it is generally a good practice to add $scope = array(); before regardless.
Loading history...
313 2
		$scope['node'] = $node;
314 2
315
		$sqlStatement = preg_replace(self::ESCAPED_INLINE_SYMBOL_REGEXP, self::INLINE_PLACEHOLDER, $sqlStatement);
316
		if ($statement->parameterMap() === null) {
317 2
			// Build a Parametermap with the inline parameters.
318 2
			// if they exist. Then delete inline infos from sqltext.
319 2
			$parameterParser = new TInlineParameterMapParser;
320 2
			$sqlText = $parameterParser->parse($sqlStatement, $scope);
321 2
			if (count($sqlText['parameters']) > 0) {
322 2
				$map = new TParameterMap();
323 2
				$map->setID($statement->getID() . '-InLineParameterMap');
324 2
				$statement->setInlineParameterMap($map);
325
				foreach ($sqlText['parameters'] as $property) {
326
					$map->addProperty($property);
327 2
				}
328
			}
329 2
			$sqlStatement = $sqlText['sql'];
330
		}
331 2
		$sqlStatement = preg_replace('/' . self::INLINE_PLACEHOLDER . '/', self::INLINE_SYMBOL, $sqlStatement);
332 2
333
		$this->prepareSql($statement, $sqlStatement, $node);
334
	}
335
336
	/**
337
	 * Prepare the sql text (may extend to dynamic sql).
338
	 * @param TSqlMapStatement $statement mapped statement.
339
	 * @param string $sqlStatement sql text.
340
	 * @param \SimpleXmlElement $node statement node.
341 2
	 * @todo Extend to dynamic sql.
342
	 */
343 2
	protected function prepareSql($statement, $sqlStatement, $node)
344 2
	{
345 2
		$simpleDynamic = new TSimpleDynamicParser;
346 2
		$sqlStatement = preg_replace(self::ESCAPED_SIMPLE_MARK_REGEXP, self::SIMPLE_PLACEHOLDER, $sqlStatement);
347 1
		$dynamics = $simpleDynamic->parse($sqlStatement);
348 1
		if (count($dynamics['parameters']) > 0) {
349
			$sql = new TSimpleDynamicSql($dynamics['parameters']);
350 2
			$sqlStatement = $dynamics['sql'];
351
		} else {
352 2
			$sql = new TStaticSql();
353 2
		}
354 2
		$sqlStatement = preg_replace('/' . self::SIMPLE_PLACEHOLDER . '/', self::SIMPLE_MARK, $sqlStatement);
355 2
		$sql->buildPreparedStatement($statement, $sqlStatement);
356
		$statement->setSqlText($sql);
357
	}
358
359
	/**
360
	 * Load select statement from xml mapping.
361 2
	 * @param \SimpleXmlElement $node select node.
362
	 */
363 2
	protected function loadSelectTag($node)
364 2
	{
365 2
		$select = new TSqlMapSelect;
366 2
		$this->setObjectPropFromNode($select, $node);
367 2
		$this->processSqlStatement($select, $node);
368
		$mappedStatement = new TMappedStatement($this->_manager, $select);
369
		$cacheModel = $select->getCacheModel();
370
		if ($cacheModel !== null && strlen($cacheModel) > 0) {
371 2
			$mappedStatement = new TCachingStatement($mappedStatement);
372 2
		}
373
374
		$this->_manager->addMappedStatement($mappedStatement);
375
	}
376
377
	/**
378
	 * Load insert statement from xml mapping.
379
	 * @param \SimpleXmlElement $node insert node.
380
	 */
381
	protected function loadInsertTag($node)
382
	{
383
		$insert = $this->createInsertStatement($node);
384
		$this->processSqlStatement($insert, $node);
385
		$mappedStatement = new TInsertMappedStatement($this->_manager, $insert);
386
		$this->_manager->addMappedStatement($mappedStatement);
387
	}
388
389
	/**
390
	 * Create new insert statement from xml node.
391
	 * @param \SimpleXmlElement $node insert node.
392
	 * @return TSqlMapInsert insert statement.
393
	 */
394
	protected function createInsertStatement($node)
395
	{
396
		$insert = new TSqlMapInsert;
397
		$this->setObjectPropFromNode($insert, $node);
398
		if (isset($node->selectKey)) {
399
			$this->loadSelectKeyTag($insert, $node->selectKey);
400
		}
401
		return $insert;
402
	}
403
404
	/**
405
	 * Load the selectKey statement from xml mapping.
406
	 * @param mixed $insert
407
	 * @param \SimpleXmlElement $node selectkey node
408
	 */
409
	protected function loadSelectKeyTag($insert, $node)
410
	{
411
		$selectKey = new TSqlMapSelectKey;
412
		$this->setObjectPropFromNode($selectKey, $node);
413
		$selectKey->setID($insert->getID());
414
		$selectKey->setID($insert->getID() . '.SelectKey');
415
		$this->processSqlStatement($selectKey, $node);
416
		$insert->setSelectKey($selectKey);
417
		$mappedStatement = new TMappedStatement($this->_manager, $selectKey);
418
		$this->_manager->addMappedStatement($mappedStatement);
419
	}
420
421
	/**
422
	 * Load update statement from xml mapping.
423
	 * @param \SimpleXmlElement $node update node.
424
	 */
425
	protected function loadUpdateTag($node)
426
	{
427
		$update = new TSqlMapUpdate;
428
		$this->setObjectPropFromNode($update, $node);
429
		$this->processSqlStatement($update, $node);
430
		$mappedStatement = new TUpdateMappedStatement($this->_manager, $update);
431
		$this->_manager->addMappedStatement($mappedStatement);
432
	}
433
434
	/**
435
	 * Load delete statement from xml mapping.
436
	 * @param \SimpleXmlElement $node delete node.
437
	 */
438
	protected function loadDeleteTag($node)
439
	{
440
		$delete = new TSqlMapDelete;
441
		$this->setObjectPropFromNode($delete, $node);
442
		$this->processSqlStatement($delete, $node);
443
		$mappedStatement = new TDeleteMappedStatement($this->_manager, $delete);
444
		$this->_manager->addMappedStatement($mappedStatement);
445
	}
446
447
	/**
448
	 * Load procedure statement from xml mapping.
449
	 * @todo Implement loading procedure
450
	 * @param \SimpleXmlElement $node procedure node
451
	 */
452
	protected function loadProcedureTag($node)
453
	{
454
		//var_dump('todo: add load procedure');
455
	}
456
457
	/**
458
	 * Load cache models from xml mapping.
459
	 * @param \SimpleXmlElement $node cache node.
460
	 */
461
	protected function loadCacheModel($node)
462
	{
463
		$cacheModel = new TSqlMapCacheModel;
464
		$properties = ['id', 'implementation'];
465
		foreach ($node->attributes() as $name => $value) {
466
			if (in_array(strtolower($name), $properties)) {
467
				$cacheModel->{'set' . $name}((string) $value);
468
			}
469
		}
470
		$cache = Prado::createComponent($cacheModel->getImplementationClass(), $cacheModel);
471
		$this->setObjectPropFromNode($cache, $node, $properties);
472
473
		foreach ($node->xpath('property') as $propertyNode) {
474
			$name = $propertyNode->attributes()->name;
475
			if ($name === null || $name === '') {
476
				continue;
477
			}
478
479
			$value = $propertyNode->attributes()->value;
480
			if ($value === null || $value === '') {
481
				continue;
482
			}
483
484
			if (!TPropertyAccess::has($cache, $name)) {
485
				continue;
486
			}
487
488
			TPropertyAccess::set($cache, $name, $value);
489
		}
490
491
		$this->loadFlushInterval($cacheModel, $node);
492
493
		$cacheModel->initialize($cache);
494
		$this->_manager->addCacheModel($cacheModel);
495
		foreach ($node->xpath('flushOnExecute') as $flush) {
496
			$this->loadFlushOnCache($cacheModel, $node, $flush);
497
		}
498
	}
499
500
	/**
501
	 * Load the flush interval
502
	 * @param TSqlMapCacheModel $cacheModel cache model
503
	 * @param \SimpleXmlElement $node cache node
504
	 */
505
	protected function loadFlushInterval($cacheModel, $node)
506
	{
507
		$flushInterval = $node->xpath('flushInterval');
508
		if ($flushInterval === null || count($flushInterval) === 0) {
509
			return;
510
		}
511
		$duration = 0;
512
		foreach ($flushInterval[0]->attributes() as $name => $value) {
513
			switch (strToLower($name)) {
514
				case 'seconds':
515
					$duration += (integer) $value;
516
				break;
517
				case 'minutes':
518
					$duration += 60 * (integer) $value;
519
				break;
520
				case 'hours':
521
					$duration += 3600 * (integer) $value;
522
				break;
523
				case 'days':
524
					$duration += 86400 * (integer) $value;
525
				break;
526
				case 'duration':
527
					$duration = (integer) $value;
528
				break 2; // switch, foreach
529
			}
530
		}
531
		$cacheModel->setFlushInterval($duration);
532
	}
533
534
	/**
535
	 * Load the flush on cache properties.
536
	 * @param TSqlMapCacheModel $cacheModel cache model
537
	 * @param \SimpleXmlElement $parent parent node.
538
	 * @param \SimpleXmlElement $node flush node.
539
	 */
540
	protected function loadFlushOnCache($cacheModel, $parent, $node)
541
	{
542
		$id = $cacheModel->getID();
543
		if (!isset($this->_FlushOnExecuteStatements[$id])) {
544
			$this->_FlushOnExecuteStatements[$id] = [];
545
		}
546
		foreach ($node->attributes() as $name => $value) {
547
			if (strtolower($name) === 'statement') {
548
				$this->_FlushOnExecuteStatements[$id][] = (string) $value;
549
			}
550
		}
551
	}
552
553 2
	/**
554
	 * Attach CacheModel to statement and register trigger statements for cache models
555 2
	 */
556
	protected function registerCacheTriggers()
557
	{
558
		foreach ($this->_FlushOnExecuteStatements as $cacheID => $statementIDs) {
559
			$cacheModel = $this->_manager->getCacheModel($cacheID);
560
			foreach ($statementIDs as $statementID) {
561
				$statement = $this->_manager->getMappedStatement($statementID);
562 2
				$cacheModel->registerTriggerStatement($statement);
563
			}
564
		}
565
	}
566
}
567