Passed
Push — master ( de3595...5928a0 )
by dima
05:09
created

AbstractDataMapper::save()   A

Complexity

Conditions 4
Paths 6

Size

Total Lines 12
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 20

Importance

Changes 9
Bugs 0 Features 0
Metric Value
c 9
b 0
f 0
dl 0
loc 12
rs 9.2
ccs 0
cts 6
cp 0
cc 4
eloc 6
nc 6
nop 1
crap 20
1
<?php
2
3
namespace SimpleORM;
4
5
/**
6
 * Description of AbstractDataMapper
7
 *
8
 * @author Dmitriy
9
 */
10
abstract class AbstractDataMapper implements RepositoryInterface, MapperInterface
11
{
12
	/**
13
	 * адаптер для работы с бд
14
	 * @var type 
15
	 */
16
	protected $adapter;
17
	
18
	/**
19
	 * таблица для сущности
20
	 * @var type 
21
	 */
22
    protected $entityTable;	
23
24
	/**
25
	 * первичный ключ
26
	 * @var type 
27
	 */
28
	protected $key;
29
		
30
	/**
31
	 * Использование join при выборке
32
	 * @var type 
33
	 */
34
	protected $use_joins = false;
35
	
36
	/**
37
	 * Использование мягкое удаление
38
	 * @var type 
39
	 */
40
	protected $use_delete = false;
41
42
	/**
43
	 * поле для мягкого удаления
44
	 * @var type 
45
	 */
46
	protected $soft_delete_key;
47
	
48
49
	/**
50
	 * поля сущности 
51
	 * @var type 
52
	 */
53
	protected $mapping_fields = [];
54
	
55
	/**
56
	 * псевдонимы полей сущности
57
	 * @var type 
58
	 */
59
	protected $mapping_fields_aliases = [];
60
	
61
	/**
62
	 * связи с другими мапперами
63
	 * @var type 
64
	 */
65
	protected $relations = [];
66
67
	/**
68
	 * Контейнер
69
	 * @var League\Container\Container
70
	 */
71
	protected $DI;
72
			
73
	function __construct(\League\Container\Container $DI, QueryBuilderInterface $adapter, $db_name = null) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
74
		
75
		$this->DI = $DI;
0 ignored issues
show
Documentation Bug introduced by
It seems like $DI of type object<League\Container\Container> is incompatible with the declared type object<SimpleORM\League\Container\Container> of property $DI.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
76
		
77
		$this->setMappingFields();
78
		
79
		$this->setAdapter($adapter);
80
		
81
		$this->setEntityTable($db_name);
82
		
83
		if($this->getEntityTable()=='' || $this->getPrimaryKey()==''){
84
			throw new InvalidEntityPropertyException('Свойства entityTable или key не заданы');
85
		}		
86
		
87
	}
88
89
	abstract protected function setMappingFields();	
90
	
91
    public function getAdapter() {
92
        return $this->adapter;
93
    }
94
95
	public function setAdapter(QueryBuilderInterface $adapter){
96
		 $this->adapter = $adapter;
0 ignored issues
show
Documentation Bug introduced by
It seems like $adapter of type object<SimpleORM\QueryBuilderInterface> is incompatible with the declared type object<SimpleORM\type> of property $adapter.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
97
	}
98
			
99
	
100
	protected function getEntityTable() {
101
		return $this->entityTable;
102
	}
103
104
	/**
105
	 * Уставнока таблицы
106
	 */
107
	protected function setEntityTable($db_name) {
108
		$this->entityTable = !empty($db_name)? "$db_name.".$this->table : $this->table;
0 ignored issues
show
Bug introduced by
The property table does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
109
	}	
110
111
	/**
112
	 * Получение записи по ключу
113
	 * @param type $id
114
	 * @return type
115
	 */
116
    public function findById($id)
117
    {
118
		$Criteria = (new Specification())->setWhere($this->key , $id);
119
		
120
        return $this->findBySpecification($Criteria);
121
    }	
122
	
123
	/**
124
	 * Сохранение сущности без спобытий
125
	 * @param \SimpleORM\EntityInterface $Entity
126
	 */
127
	public function saveWithoutEvents(EntityInterface $Entity) {
128
		
129
		$data = $this->unbuildEntity($Entity);
130
		
131
		$id = $data[$this->getPrimaryKey()];
132
		unset($data[$this->getPrimaryKey()]);		
133
			
134
		//insert
135
		if (empty($id)) {
136
			
137
			unset($data[$this->setSoftDeleteKey()]);
138
			
139
			$this->getAdapter()->insert($this->getEntityTable(),$data);
140
			
141
			if (!$id = $this->getAdapter()->insert_id()) {
142
				return false;
143
			}
144
			
145
			$Entity->setId($id);
146
		}
147
		//update
148
		else {
149
			
150
			if(!$this->getAdapter()->update($this->getEntityTable(), $data, "{$this->getPrimaryKey()} = '{$id}'")){
151
				return false;
152
			}
153
154
		}	
155
		
156
		return true;
157
	}
158
	
159
	/**
160
	 * Cохранение сущности
161
	 * @param EntityInterface $Entity
162
	 */
163
	public function save(EntityInterface $Entity)
164
	{
165
		if(method_exists($this, 'onAfterSave' )) $this->onAfterSave( $Entity );
166
		
167
		if(!$this->saveWithoutEvents($Entity)){
168
			return false;
169
		}
170
		
171
		if(method_exists($this, 'onBeforeSave' )) $this->onBeforeSave( $Entity );
172
173
		return true;
174
	}
175
	
176
	/**
177
	 * Событие перед сохранением
178
	 * @param \SimpleORM\EntityInterface $Entity
179
	 */
180
	public function onAfterSave(EntityInterface $Entity){
0 ignored issues
show
Unused Code introduced by
The parameter $Entity is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
181
		
182
		$this->getAdapter()->startTransaction();
183
		
184
		$rel_list = $this->createListRelation();
185
186
		foreach ($rel_list as $mapper => $obj_path){
187
188
			$get_path = str_replace('#','->get','$o = $Entity'.$obj_path.';');
189
			$set_path = str_replace(['#','();'],['->set','($o);'],'$Entity'.$obj_path.';');
190
			eval($get_path);//получаем объект таким образом дабы не гулять по корневому объекту
0 ignored issues
show
Coding Style introduced by
It is generally not recommended to use eval unless absolutely required.

On one hand, eval might be exploited by malicious users if they somehow manage to inject dynamic content. On the other hand, with the emergence of faster PHP runtimes like the HHVM, eval prevents some optimization that they perform.

Loading history...
191
			if($this->DI->get($mapper)->saveWithoutEvents($o)){
0 ignored issues
show
Bug introduced by
The variable $o does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
192
				eval($set_path);
0 ignored issues
show
Coding Style introduced by
It is generally not recommended to use eval unless absolutely required.

On one hand, eval might be exploited by malicious users if they somehow manage to inject dynamic content. On the other hand, with the emergence of faster PHP runtimes like the HHVM, eval prevents some optimization that they perform.

Loading history...
193
			}
194
			unset($o);
195
		}
196
		
197
	}
198
	
199
	/**
200
	 * После успешного сохранения
201
	 * @param \SimpleORM\EntityInterface $Entity
202
	 */
203
	protected function onBeforeSave(EntityInterface $Entity){
0 ignored issues
show
Unused Code introduced by
The parameter $Entity is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
204
		
205
		$this->getAdapter()->endTransaction();
206
		
207
//		foreach ($this->relations as $alias => $mapper) {
0 ignored issues
show
Unused Code Comprehensibility introduced by
57% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
208
//		
209
//			$SaveEntity = $Entity->{'get'.$alias}();
210
//			
211
//			if(!$mapper->save($SaveEntity)){
212
//				throw new \Autoprice\Exceptions\EntityNotSaveException('Unable to save Entity!');
213
//			}
214
//			
215
//			unset($SaveEntity);
216
//		}
217
//		
218
//		return true;
219
	}	
220
221
	
222
	/**
223
	 * получение мапперов в порядке их использования с учетом вложенности
224
	 * @return array
225
	 */
226
	protected function createListRelation(){
227
		$rel_list = [];
228
		
229
		$rel_map = $this->getRelations();
230
		
231
		$this->createListRelationReq($rel_map,$rel_list);
0 ignored issues
show
Documentation introduced by
$rel_list is of type array, but the function expects a object<SimpleORM\type>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
232
		
233
		return $rel_list;
234
	}
235
	
236
	/**
237
	 * Выстроивает порядок использования мапперов в иерархии
238
	 * @param array $rel_map
239
	 * @param type $rel_list
240
	 */
241
	protected function createListRelationReq(array $rel_map,&$rel_list,$obj_parent_link = null) {
242
		
243
		foreach ($rel_map as $rel){
244
			
245
			$obj_link = $parent.'#'.$rel['alias'].'()';
0 ignored issues
show
Bug introduced by
The variable $parent does not exist. Did you mean $obj_parent_link?

This check looks for variables that are accessed but have not been defined. It raises an issue if it finds another variable that has a similar name.

The variable may have been renamed without also renaming all references.

Loading history...
246
			
247
			if(count($rel['relations'])>0){
248
				$this->createListRelationReq($rel['relations'],$rel_list,$parent.'->get'.$rel['alias'].'()');
0 ignored issues
show
Bug introduced by
The variable $parent does not exist. Did you mean $obj_parent_link?

This check looks for variables that are accessed but have not been defined. It raises an issue if it finds another variable that has a similar name.

The variable may have been renamed without also renaming all references.

Loading history...
249
				$rel_list [$rel['name']]= $obj_parent_link.$obj_link;
250
			}
251
			else{
252
				$rel_list [$rel['name']] = $obj_parent_link.$obj_link;
253
			}
254
		}
255
	}
256
	
257
	
258
	/**
259
	 * На успешное удаление
260
	 * @param \SimpleORM\EntityInterface $Entity
261
	 */
262
	protected function onBeforeDelete(EntityInterface $Entity) {
263
		foreach ($this->relations as $alias => $cfg) {
264
			$mapper = $cfg['mapper'];
265
			//если связь один к одному то удаляем сущность
266
			if($cgg['reltype'] == 'has_one'){
0 ignored issues
show
Bug introduced by
The variable $cgg does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
267
				$Entity = $Entity->{'get'.$alias}();
268
				if(!$mapper->delete($Entity)){
269
					throw new \Autoprice\Exceptions\EntityNotDeleteException('Unable to delete Entity!');
270
				}
271
			}
272
		}
273
		
274
		return true;
275
	}	
276
	
277
	
278
	
279
	/**
280
	 * получить связи
281
	 */
282
	protected function getRelations(){
283
		$rel_map = [];
284
		foreach ($this->mapping_fields as $field => $cfg){
285
			if(isset($cfg['relation'])){		
286
				$rels = $this->DI->get($cfg['relation'])->getRelations();
287
				$rel_map []= [
288
					'name'		=>	$cfg['relation'],
289
					'alias'		=>	$field,
290
					'relations'	=>	$rels
291
				];
292
			}
293
		}
294
		return $rel_map;
295
	}
296
297
	
298
299
300
301
302
303
	/**
304
	 * Подготавливаем конечный вариант Сущности
305
	 * 
306
	 * @param \Core\Infrastructure\EntityInterface $Entity
307
	 * @param array $row
308
	 * @return \Core\Infrastructure\EntityInterface
309
	 * @throws BadMethodCallException
310
	 */
311
	protected function buildEntity(EntityInterface $Entity, array $row){
312
		
313
        foreach ($this->mapping_fields as $alias => $cfg ) {
314
			
315
			$value = false;
316
			
317
			$field = $cfg['field'];
318
			
319
			$method_set = 'set' . ucfirst($alias);
320
			
321
			if(!method_exists($Entity, $method_set )){
322
				throw new BadMethodCallException("Метод {$method_set}  не определен");
323
			}			
324
			
325
			//событие на формирование поля
326
			if( isset($cfg['build']) && is_object($cfg['build']) ){
327
				$value = call_user_func($cfg['build'], $row);
328
			}
329
			//на связь
330
			elseif(isset($cfg['relation'])){
331
				
332
				$mapper = $this->DI->get($cfg['relation']);
333
				
334
				if($this->use_joins===true || empty($row[$field])){
335
					$value = $mapper->createEntity($row);
336
				}
337
				else{
338
					$fkey = isset($cfg['on']) ? $cfg['on'] :$mapper->key;
339
					$value = $mapper->findBySpecification((new Specification)->setWhere($fkey, $row[$field]));
340
				}				
341
				
342
			}
343
			elseif(is_string($field) && isset($row[strtolower($field)])){
344
				$value = $row[strtolower($field)];
345
			}
346
			
347
			if($value!==false)
348
				$Entity->{$method_set}($value);
349
			
350
        }
351
		
352
        return $Entity;		
353
	}	
354
355
	
356
	/**
357
	 * из объекта формирует массив
358
	 * @param \Core\Infrastructure\EntityInterface $Entity
359
	 * @return \Core\Infrastructure\EntityInterface
360
	 * @throws BadMethodCallException
361
	 */
362
	protected function unbuildEntity(EntityInterface $Entity){
363
		
364
		$row = [];
365
366
        foreach ($this->mapping_fields as $alias => $cfg ) {
367
			
368
			$field = $cfg['field'];
369
			
370
			$method_get = 'get' . ucfirst($alias);
371
			
372
			if(!method_exists($Entity, $method_get )){
373
				throw new BadMethodCallException("Метод {$method_get}  не определен");
374
			}		
375
			
376
			//--------------------------------------------------------------------
377
			if( isset($cfg['unbuild']) && is_object($cfg['unbuild']) ){
378
				$value = call_user_func($cfg['unbuild'], $Entity->{$method_get}() );
379
			}
380
			elseif(isset($cfg['relation'])){
381
				
382
				if(isset($cfg['on']))
383
					$fkey = $this->DI->get($cfg['relation'])->getFieldAlias($cfg['on']);
384
				else
385
					$fkey = 'id';
386
				
387
				$value = $Entity->{$method_get}()->{'get'.$fkey}();
388
				
389
			}			
390
			else{
391
				$value = $Entity->{$method_get}();
392
			}			
393
						
394
			$row[$field] = $value;
395
396
        }
397
			
398
        return $row;		
399
	}	
400
	
401
	/**
402
	 * Установка поля для маппинга
403
	 */
404 5
	protected function addMappingField($alias,$field = null){
405
		
406 5
		if(is_string($field)){
407 1
			$field = ['field'	=>	$field];
408 1
		}
409 4
		elseif( (is_array($field) && !isset($field['field'])) || empty($field)){
410 1
			$field['field']	= $alias;
411 1
		}
412
	
413 5
		$this->mapping_fields[$alias] = $field;
414
415 5
		if(isset($field['primary']) && $field['primary']===true){
416 1
			$this->key = $field['field'];
417 1
		}
418
		
419 5
		if(isset($field['softdelete']) && $field['softdelete']===true){
420 1
			$this->soft_delete_key = $field['field'];
421 1
		}
422
		
423 5
		$this->mapping_fields_aliases[$field['field']] = $alias;
424
		
425 5
		return $this;
426
	}	
427
	
428
429
	
430
	/**
431
	 * Установка ключа
432
	 */
433
	protected function getPrimaryKey() {
434
		return $this->key;
435
	}	
436
	
437
	/**
438
	 * Устанвка поля для мягкого удаляения
439
	 */
440
	protected function setSoftDeleteKey() {
441
		return $this->soft_delete_key;
442
	}
443
444
445
	
446
	public function getFieldAlias($field){
447
		
448
		return $this->mapping_fields_aliases[$field];
449
		
450
	}	
451
	
452
	
453
	/**
454
	 * 
455
	 * @param ISpecificationCriteria $specification
456
	 * @return type
457
	 */
458
	public function findBySpecification(ISpecificationCriteria $specification){
459
460
		//псеводо удаление
461
		$this->setSoftDelete($specification);
462
		
463
		$this->setRelations($specification);
464
		
465
		$specification->setLimit(1);
466
		
467
		//получение записей
468
		$res = $this->getAdapter()->getResultQuery($this->getEntityTable(),$specification);
469
470
        if (!$row = $res->row_array()) {
471
            return null;
472
        }
473
        return $this->createEntity($row);				
474
	}
475
	
476
	/**
477
	 * Удаление записи
478
	 * @param EntityInterface $Entity
479
	 * @return boolean
480
	 */
481
	public function delete(EntityInterface $Entity)
482
	{
483
		$result = false;
484
		
485
		$delete_key = $this->setSoftDeleteKey();
486
		
487
		if (
488
				$delete_key > '' && 
489
				$Entity->getId() > 0){
490
				$result = $this->getAdapter()->update($this->getEntityTable(), [ $delete_key => 1 ], "{$this->getPrimaryKey()} = '{$Entity->getId()}'");
491
		}
492
		elseif($Entity->getId() > 0){
493
			
494
			if($result = $this->getAdapter()->delete($this->getEntityTable(), $this->getPrimaryKey()."  = ".$Entity->getId())){
495
				if(method_exists($this, 'onBeforeDelete' )){ $result = $this->onBeforeDelete( $Entity );}
496
			}
497
		}
498
		
499
		return $result;
500
	}
501
502
	public function findAllBySpecification(ISpecificationCriteria $specification)
503
	{
504
505
		$entities = [];
0 ignored issues
show
Unused Code introduced by
$entities is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
506
		
507
		//псеводо удаление
508
		$this->setSoftDelete($specification);		
509
		
510
		$this->setRelations($specification);
511
		
512
		$res = $this->getAdapter()->getResultQuery($this->getEntityTable(),$specification);
513
		
514
		if (!$rows = $res->result_array()) {
515
            return null;
516
        }	
517
		
518
		foreach($rows as $k =>  $row){
519
			$rows[$k] = $this->createEntity($row);
520
		}
521
		
522
		return $rows;		
523
	}
524
525
	public function findAll()
526
	{
527
		return $this->findAllBySpecification(new Specification());
528
	}
529
	
530
	/**
531
	 * Выборка удаленных моделей
532
	 * @param ISpecificationCriteria $specification
533
	 */
534
	private function setSoftDelete(ISpecificationCriteria $specification){
535
		if(
536
				$this->use_delete === false &&
537
				$this->setSoftDeleteKey()>'' 
538
				&& !isset($specification->getWhere()[$this->setSoftDeleteKey()])
539
				)
540
		$specification->setWhere($this->setSoftDeleteKey(),0);
0 ignored issues
show
Documentation introduced by
0 is of type integer, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
541
	}
542
	
543
	/**
544
	 * Построение join-ов
545
	 * 
546
	 * @todo добавить типы связей 
547
	 * has_many - один к многим (пост и коммеентарии)
548
	 * belongs_to - многие к многим (пользователь имет множество оплат одного заказа)
549
	 * has_one - один к одному
550
	 */
551
	protected function setRelations(ISpecificationCriteria $Specification){
552
553
		$joins = [];
554
555
		foreach ($this->mapping_fields as $field => $cfg){
556
			if(isset($cfg['relation'])){
557
				
558
				$this->relations[$field] = [
559
					'mapper'	=>	$mapper = $this->DI->get($cfg['relation']),
560
					'reltype'	=>  isset($cfg['reltype']) ? $cfg['reltype'] : 'belongs_to'
561
				];
562
563
				$table = $mapper->getEntityTable();
564
565
				$relation_key = isset($cfg['on'])? $cfg['on'] : $mapper->key;
566
				
567
				$joins[$table] = [
568
						'alias'	=> $field,
569
						//'type'	=> 'INNER',
0 ignored issues
show
Unused Code Comprehensibility introduced by
67% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
570
						'on'	=> "`{$this->table}`.{$cfg['field']} = `{$field}`.{$relation_key}"
571
				];
572
573
			}
574
		}	
575
576
		if($this->use_joins===true){
577
			$Specification->setJoins($joins);
578
		}			
579
	}
580
	
581
	/**
582
	 * Использование join-ов
583
	 */
584
	public function useJoins()
585
	{
586
		$o = clone $this;
587
		$o->use_joins = true;
0 ignored issues
show
Documentation Bug introduced by
It seems like true of type boolean is incompatible with the declared type object<SimpleORM\type> of property $use_joins.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
588
		return $o;
589
	}
590
	
591
	public function withDelete(){
592
		$o = clone $this;
593
		$o->use_delete = true;
0 ignored issues
show
Documentation Bug introduced by
It seems like true of type boolean is incompatible with the declared type object<SimpleORM\type> of property $use_delete.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
594
		return $o;
595
	}
596
	
597
}
598