Test Failed
Branch master (206474)
by Fabio
18:24
created

TSqlMapXmlMappingConfiguration::loadInsertTag()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 5
nc 1
nop 1
dl 0
loc 7
rs 9.4285
c 0
b 0
f 0
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
 * @copyright Copyright &copy; 2005-2016 The PRADO Group
8
 * @license https://github.com/pradosoft/prado/blob/master/LICENSE
9
 * @package Prado\Data\SqlMap\Configuration
10
 */
11
12
namespace Prado\Data\SqlMap\Configuration;
13
14
use Prado\Caching\TFileCacheDependency;
15
use Prado\Data\SqlMap\DataMapper\TPropertyAccess;
16
use Prado\Data\SqlMap\DataMapper\TSqlMapConfigurationException;
17
use Prado\Data\SqlMap\Statements\TCachingStatement;
18
use Prado\Data\SqlMap\Statements\TDeleteMappedStatement;
19
use Prado\Data\SqlMap\Statements\TInsertMappedStatement;
20
use Prado\Data\SqlMap\Statements\TMappedStatement;
21
use Prado\Data\SqlMap\Statements\TSimpleDynamicSql;
22
use Prado\Data\SqlMap\Statements\TStaticSql;
23
use Prado\Data\SqlMap\Statements\TUpdateMappedStatement;
24
use Prado\Prado;
25
26
/**
27
 * Loads the statements, result maps, parameters maps from xml configuration.
28
 *
29
 * description
30
 *
31
 * @author Wei Zhuo <weizho[at]gmail[dot]com>
32
 * @package Prado\Data\SqlMap\Configuration
33
 * @since 3.1
34
 */
35
class TSqlMapXmlMappingConfiguration extends TSqlMapXmlConfigBuilder
36
{
37
	private $_xmlConfig;
38
	private $_configFile;
39
	private $_manager;
40
41
	private $_document;
42
43
	private $_FlushOnExecuteStatements = [];
44
45
	/**
46
	 * Regular expressions for escaping simple/inline parameter symbols
47
	 */
48
	const SIMPLE_MARK = '$';
49
	const INLINE_SYMBOL = '#';
50
	const ESCAPED_SIMPLE_MARK_REGEXP = '/\$\$/';
51
	const ESCAPED_INLINE_SYMBOL_REGEXP = '/\#\#/';
52
	const SIMPLE_PLACEHOLDER = '`!!`';
53
	const INLINE_PLACEHOLDER = '`!!!`';
54
55
	/**
56
	 * @param TSqlMapXmlConfiguration $xmlConfig parent xml configuration.
57
	 */
58
	public function __construct(TSqlMapXmlConfiguration $xmlConfig)
59
	{
60
		$this->_xmlConfig = $xmlConfig;
61
		$this->_manager = $xmlConfig->getManager();
62
	}
63
64
	protected function getConfigFile()
65
	{
66
		return $this->_configFile;
67
	}
68
69
	/**
70
	 * Configure an XML mapping.
71
	 * @param string $filename xml mapping filename.
72
	 */
73
	public function configure($filename)
74
	{
75
		$this->_configFile = $filename;
76
		$document = $this->loadXmlDocument($filename, $this->_xmlConfig);
77
		$this->_document = $document;
78
79
		static $bCacheDependencies;
80
		if ($bCacheDependencies === null) {
81
			$bCacheDependencies = true;
82
		} //Prado::getApplication()->getMode() !== TApplicationMode::Performance;
83
84
		if ($bCacheDependencies) {
85
			$this->_manager->getCacheDependencies()
86
					->getDependencies()
87
					->add(new TFileCacheDependency($filename));
88
		}
89
90
		foreach ($document->xpath('//resultMap') as $node) {
91
			$this->loadResultMap($node);
92
		}
93
94
		foreach ($document->xpath('//parameterMap') as $node) {
95
			$this->loadParameterMap($node);
96
		}
97
98
		foreach ($document->xpath('//statement') as $node) {
99
			$this->loadStatementTag($node);
100
		}
101
102
		foreach ($document->xpath('//select') as $node) {
103
			$this->loadSelectTag($node);
104
		}
105
106
		foreach ($document->xpath('//insert') as $node) {
107
			$this->loadInsertTag($node);
108
		}
109
110
		foreach ($document->xpath('//update') as $node) {
111
			$this->loadUpdateTag($node);
112
		}
113
114
		foreach ($document->xpath('//delete') as $node) {
115
			$this->loadDeleteTag($node);
116
		}
117
118
		foreach ($document->xpath('//procedure') as $node) {
119
			$this->loadProcedureTag($node);
120
		}
121
122
		foreach ($document->xpath('//cacheModel') as $node) {
123
			$this->loadCacheModel($node);
124
		}
125
126
		$this->registerCacheTriggers();
127
	}
128
129
	/**
130
	 * Load the result maps.
131
	 * @param SimpleXmlElement $node result map node.
132
	 */
133
	protected function loadResultMap($node)
134
	{
135
		$resultMap = $this->createResultMap($node);
136
137
		//find extended result map.
138
		if (strlen($extendMap = $resultMap->getExtends()) > 0) {
139 View Code Duplication
			if (!$this->_manager->getResultMaps()->contains($extendMap)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
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
221
		if (strlen($extendMap = $parameterMap->getExtends()) > 0) {
222 View Code Duplication
			if (!$this->_manager->getParameterMaps()->contains($extendMap)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
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
	 */
267 View Code Duplication
	protected function loadStatementTag($node)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
268
	{
269
		$statement = new TSqlMapStatement();
270
		$this->setObjectPropFromNode($statement, $node);
271
		$this->processSqlStatement($statement, $node);
272
		$mappedStatement = new TMappedStatement($this->_manager, $statement);
273
		$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
	 */
282
	protected function processSqlStatement($statement, $node)
283
	{
284
		$commandText = (string) $node;
285
		if (strlen($extend = $statement->getExtends()) > 0) {
286
			$superNode = $this->getElementByIdValue($this->_document, '*', $extend);
287
			if ($superNode !== null) {
288
				$commandText = (string) $superNode . $commandText;
289
			} else {
290
				throw new TSqlMapConfigurationException(
291
						'sqlmap_unable_to_find_parent_sql',
292
					$extend,
293
					$this->_configFile,
294
					$node
295
				);
296
			}
297
		}
298
		//$commandText = $this->_xmlConfig->replaceProperties($commandText);
299
		$statement->initialize($this->_manager);
300
		$this->applyInlineParameterMap($statement, $commandText, $node);
301
	}
302
303
	/**
304
	 * Extract inline parameter maps.
305
	 * @param TSqlMapStatement $statement statement object.
306
	 * @param string $sqlStatement sql text
307
	 * @param SimpleXmlElement $node statement node.
308
	 */
309
	protected function applyInlineParameterMap($statement, $sqlStatement, $node)
310
	{
311
		$scope['file'] = $this->_configFile;
0 ignored issues
show
Coding Style Comprehensibility introduced by
$scope was never initialized. Although not strictly required by PHP, it is generally a good practice to add $scope = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
312
		$scope['node'] = $node;
313
314
		$sqlStatement = preg_replace(self::ESCAPED_INLINE_SYMBOL_REGEXP, self::INLINE_PLACEHOLDER, $sqlStatement);
315
		if ($statement->parameterMap() === null) {
316
			// Build a Parametermap with the inline parameters.
317
			// if they exist. Then delete inline infos from sqltext.
318
			$parameterParser = new TInlineParameterMapParser;
319
			$sqlText = $parameterParser->parse($sqlStatement, $scope);
320
			if (count($sqlText['parameters']) > 0) {
321
				$map = new TParameterMap();
322
				$map->setID($statement->getID() . '-InLineParameterMap');
323
				$statement->setInlineParameterMap($map);
324
				foreach ($sqlText['parameters'] as $property) {
325
					$map->addProperty($property);
326
				}
327
			}
328
			$sqlStatement = $sqlText['sql'];
329
		}
330
		$sqlStatement = preg_replace('/' . self::INLINE_PLACEHOLDER . '/', self::INLINE_SYMBOL, $sqlStatement);
331
332
		$this->prepareSql($statement, $sqlStatement, $node);
333
	}
334
335
	/**
336
	 * Prepare the sql text (may extend to dynamic sql).
337
	 * @param TSqlMapStatement $statement mapped statement.
338
	 * @param string $sqlStatement sql text.
339
	 * @param SimpleXmlElement $node statement node.
340
	 * @todo Extend to dynamic sql.
341
	 */
342
	protected function prepareSql($statement, $sqlStatement, $node)
343
	{
344
		$simpleDynamic = new TSimpleDynamicParser;
345
		$sqlStatement = preg_replace(self::ESCAPED_SIMPLE_MARK_REGEXP, self::SIMPLE_PLACEHOLDER, $sqlStatement);
346
		$dynamics = $simpleDynamic->parse($sqlStatement);
347
		if (count($dynamics['parameters']) > 0) {
348
			$sql = new TSimpleDynamicSql($dynamics['parameters']);
349
			$sqlStatement = $dynamics['sql'];
350
		} else {
351
			$sql = new TStaticSql();
352
		}
353
		$sqlStatement = preg_replace('/' . self::SIMPLE_PLACEHOLDER . '/', self::SIMPLE_MARK, $sqlStatement);
354
		$sql->buildPreparedStatement($statement, $sqlStatement);
355
		$statement->setSqlText($sql);
356
	}
357
358
	/**
359
	 * Load select statement from xml mapping.
360
	 * @param SimpleXmlElement $node select node.
361
	 */
362
	protected function loadSelectTag($node)
363
	{
364
		$select = new TSqlMapSelect;
365
		$this->setObjectPropFromNode($select, $node);
366
		$this->processSqlStatement($select, $node);
367
		$mappedStatement = new TMappedStatement($this->_manager, $select);
368
		if (strlen($select->getCacheModel()) > 0) {
369
			$mappedStatement = new TCachingStatement($mappedStatement);
370
		}
371
372
		$this->_manager->addMappedStatement($mappedStatement);
373
	}
374
375
	/**
376
	 * Load insert statement from xml mapping.
377
	 * @param SimpleXmlElement $node insert node.
378
	 */
379
	protected function loadInsertTag($node)
380
	{
381
		$insert = $this->createInsertStatement($node);
382
		$this->processSqlStatement($insert, $node);
383
		$mappedStatement = new TInsertMappedStatement($this->_manager, $insert);
384
		$this->_manager->addMappedStatement($mappedStatement);
385
	}
386
387
	/**
388
	 * Create new insert statement from xml node.
389
	 * @param SimpleXmlElement $node insert node.
390
	 * @return TSqlMapInsert insert statement.
391
	 */
392
	protected function createInsertStatement($node)
393
	{
394
		$insert = new TSqlMapInsert;
395
		$this->setObjectPropFromNode($insert, $node);
396
		if (isset($node->selectKey)) {
397
			$this->loadSelectKeyTag($insert, $node->selectKey);
398
		}
399
		return $insert;
400
	}
401
402
	/**
403
	 * Load the selectKey statement from xml mapping.
404
	 * @param mixed $insert
405
	 * @param SimpleXmlElement $node selectkey node
406
	 */
407
	protected function loadSelectKeyTag($insert, $node)
408
	{
409
		$selectKey = new TSqlMapSelectKey;
410
		$this->setObjectPropFromNode($selectKey, $node);
411
		$selectKey->setID($insert->getID());
412
		$selectKey->setID($insert->getID() . '.SelectKey');
413
		$this->processSqlStatement($selectKey, $node);
414
		$insert->setSelectKey($selectKey);
415
		$mappedStatement = new TMappedStatement($this->_manager, $selectKey);
416
		$this->_manager->addMappedStatement($mappedStatement);
417
	}
418
419
	/**
420
	 * Load update statement from xml mapping.
421
	 * @param SimpleXmlElement $node update node.
422
	 */
423 View Code Duplication
	protected function loadUpdateTag($node)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
424
	{
425
		$update = new TSqlMapUpdate;
426
		$this->setObjectPropFromNode($update, $node);
427
		$this->processSqlStatement($update, $node);
428
		$mappedStatement = new TUpdateMappedStatement($this->_manager, $update);
429
		$this->_manager->addMappedStatement($mappedStatement);
430
	}
431
432
	/**
433
	 * Load delete statement from xml mapping.
434
	 * @param SimpleXmlElement $node delete node.
435
	 */
436 View Code Duplication
	protected function loadDeleteTag($node)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
437
	{
438
		$delete = new TSqlMapDelete;
439
		$this->setObjectPropFromNode($delete, $node);
440
		$this->processSqlStatement($delete, $node);
441
		$mappedStatement = new TDeleteMappedStatement($this->_manager, $delete);
442
		$this->_manager->addMappedStatement($mappedStatement);
443
	}
444
445
	/**
446
	 * Load procedure statement from xml mapping.
447
	 * @todo Implement loading procedure
448
	 * @param SimpleXmlElement $node procedure node
449
	 */
450
	protected function loadProcedureTag($node)
451
	{
452
		//var_dump('todo: add load procedure');
453
	}
454
455
	/**
456
	 * Load cache models from xml mapping.
457
	 * @param SimpleXmlElement $node cache node.
458
	 */
459
	protected function loadCacheModel($node)
460
	{
461
		$cacheModel = new TSqlMapCacheModel;
462
		$properties = ['id', 'implementation'];
463
		foreach ($node->attributes() as $name => $value) {
464
			if (in_array(strtolower($name), $properties)) {
465
				$cacheModel->{'set' . $name}((string) $value);
466
			}
467
		}
468
		$cache = Prado::createComponent($cacheModel->getImplementationClass(), $cacheModel);
469
		$this->setObjectPropFromNode($cache, $node, $properties);
470
471
		foreach ($node->xpath('property') as $propertyNode) {
472
			$name = $propertyNode->attributes()->name;
473
			if ($name === null || $name === '') {
474
				continue;
475
			}
476
477
			$value = $propertyNode->attributes()->value;
478
			if ($value === null || $value === '') {
479
				continue;
480
			}
481
482
			if (!TPropertyAccess::has($cache, $name)) {
483
				continue;
484
			}
485
486
			TPropertyAccess::set($cache, $name, $value);
487
		}
488
489
		$this->loadFlushInterval($cacheModel, $node);
490
491
		$cacheModel->initialize($cache);
492
		$this->_manager->addCacheModel($cacheModel);
493
		foreach ($node->xpath('flushOnExecute') as $flush) {
494
			$this->loadFlushOnCache($cacheModel, $node, $flush);
495
		}
496
	}
497
498
	/**
499
	 * Load the flush interval
500
	 * @param TSqlMapCacheModel $cacheModel cache model
501
	 * @param SimpleXmlElement $node cache node
502
	 */
503
	protected function loadFlushInterval($cacheModel, $node)
504
	{
505
		$flushInterval = $node->xpath('flushInterval');
506
		if ($flushInterval === null || count($flushInterval) === 0) {
507
			return;
508
		}
509
		$duration = 0;
510
		foreach ($flushInterval[0]->attributes() as $name => $value) {
511
			switch (strToLower($name)) {
512
				case 'seconds':
513
					$duration += (integer) $value;
514
				break;
515
				case 'minutes':
516
					$duration += 60 * (integer) $value;
517
				break;
518
				case 'hours':
519
					$duration += 3600 * (integer) $value;
520
				break;
521
				case 'days':
522
					$duration += 86400 * (integer) $value;
523
				break;
524
				case 'duration':
525
					$duration = (integer) $value;
526
				break 2; // switch, foreach
527
			}
528
		}
529
		$cacheModel->setFlushInterval($duration);
530
	}
531
532
	/**
533
	 * Load the flush on cache properties.
534
	 * @param TSqlMapCacheModel $cacheModel cache model
535
	 * @param SimpleXmlElement $parent parent node.
536
	 * @param SimpleXmlElement $node flush node.
537
	 */
538
	protected function loadFlushOnCache($cacheModel, $parent, $node)
539
	{
540
		$id = $cacheModel->getID();
541
		if (!isset($this->_FlushOnExecuteStatements[$id])) {
542
			$this->_FlushOnExecuteStatements[$id] = [];
543
		}
544
		foreach ($node->attributes() as $name => $value) {
545
			if (strtolower($name) === 'statement') {
546
				$this->_FlushOnExecuteStatements[$id][] = (string) $value;
547
			}
548
		}
549
	}
550
551
	/**
552
	 * Attach CacheModel to statement and register trigger statements for cache models
553
	 */
554
	protected function registerCacheTriggers()
555
	{
556
		foreach ($this->_FlushOnExecuteStatements as $cacheID => $statementIDs) {
557
			$cacheModel = $this->_manager->getCacheModel($cacheID);
558
			foreach ($statementIDs as $statementID) {
559
				$statement = $this->_manager->getMappedStatement($statementID);
560
				$cacheModel->registerTriggerStatement($statement);
561
			}
562
		}
563
	}
564
}
565