AbstractDataMapper::delete()   B
last analyzed

Complexity

Conditions 6
Paths 5

Size

Total Lines 20
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 42

Importance

Changes 4
Bugs 0 Features 0
Metric Value
c 4
b 0
f 0
dl 0
loc 20
ccs 0
cts 13
cp 0
rs 8.8571
cc 6
eloc 11
nc 5
nop 1
crap 42
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
	use TraitDataMapperEvent;
14
	
15
	
16
	/**
17
	 * адаптер для работы с бд
18
	 * @var type 
19
	 */
20
	protected $adapter;
21
	
22
	/**
23
	 * таблица для сущности
24
	 * @var type 
25
	 */
26
    protected $entityTable;	
27
28
	/**
29
	 * первичный ключ
30
	 * @var type 
31
	 */
32
	protected $key;
33
		
34
	/**
35
	 * Использование join при выборке
36
	 * @var type 
37
	 */
38
	protected $use_joins = false;
39
	
40
	/**
41
	 * Использование мягкое удаление
42
	 * @var type 
43
	 */
44
	protected $use_delete = false;
45
46
	/**
47
	 * поле для мягкого удаления
48
	 * @var type 
49
	 */
50
	protected $soft_delete_key;
51
	
52
53
	/**
54
	 * поля сущности 
55
	 * @var type 
56
	 */
57
	protected $mapping_fields = [];
58
	
59
	/**
60
	 * псевдонимы полей сущности
61
	 * @var type 
62
	 */
63
	protected $mapping_fields_aliases = [];
64
	
65
	/**
66
	 * связи с другими мапперами
67
	 * @var type 
68
	 */
69
	protected $relations = [];
70
71
	/**
72
	 * Контейнер
73
	 * @var League\Container\Container
74
	 */
75
	protected $DI;
76
	
77
	/**
78
	 * Возврат данных в массиве
79
	 * @var type 
80
	 */
81
	protected $result_array = false;
82
			
83
	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...
84
		
85
		$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...
86
		
87
		$this->setMappingFields();
88
		
89
		$this->setAdapter($adapter);
90
		
91
		$this->setEntityTable($db_name);
92
		
93
		if($this->getEntityTable()=='' || $this->getPrimaryKey()==''){
94
			throw new InvalidEntityPropertyException('Свойства entityTable или key не заданы');
95
		}		
96
		
97
	}
98
	
99
	abstract protected function setMappingFields();	
100
	
101
    public function getAdapter() {
102
        return $this->adapter;
103
    }
104
105
	public function setAdapter(QueryBuilderInterface $adapter){
106
		 $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...
107
	}
108
			
109
	
110
	protected function getEntityTable() {
111
		return $this->entityTable;
112
	}
113
114
	/**
115
	 * Уставнока таблицы
116
	 */
117
	protected function setEntityTable($db_name) {
118
		$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...
119
	}	
120
121
	/**
122
	 * Получение записи по ключу
123
	 * @param type $id
124
	 * @return type
125
	 */
126
    public function findById($id)
127
    {
128
		$Criteria = (new Specification())->setWhere($this->key , $id);
129
		
130
        return $this->findBySpecification($Criteria);
131
    }	
132
	
133
	/**
134
	 * Сохранение сущности без спобытий
135
	 * @param \SimpleORM\EntityInterface $Entity
136
	 */
137
	public function saveWithoutEvents(EntityInterface $Entity) {
138
		
139
		$data = $this->unbuildEntity($Entity);
140
		
141
		if(method_exists($this, 'onPrepareData' )) $this->onPrepareData( $Entity , $data );
0 ignored issues
show
Documentation introduced by
$data 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...
142
		
143
		$id = $data[$this->getPrimaryKey()];
144
		unset($data[$this->getPrimaryKey()]);		
145
			
146
		//insert
147
		if (empty($id)) {
148
			
149
			unset($data[$this->setSoftDeleteKey()]);
150
			
151
			$this->getAdapter()->insert($this->getEntityTable(),$data);
152
			
153
			if (!$id = $this->getAdapter()->insert_id()) {
154
				return false;
155
			}
156
			
157
			$Entity->setId($id);
158
		}
159
		//update
160
		else {
161
			
162
			if(!$this->getAdapter()->update($this->getEntityTable(), $data, "{$this->getPrimaryKey()} = '{$id}'")){
163
				return false;
164
			}
165
166
		}	
167
		
168
		return true;
169
	}
170
	
171
	/**
172
	 * Cохранение сущности
173
	 * @param EntityInterface $Entity
174
	 */
175
	public function save(EntityInterface $Entity)
176
	{
177
		if(method_exists($this, 'onAfterSave' )) $this->onAfterSave( $Entity );
178
		
179
		if(!$this->saveWithoutEvents($Entity)){
180
			return false;
181
		}
182
		
183
		if(method_exists($this, 'onBeforeSave' )) $this->onBeforeSave( $Entity );
184
185
		return true;
186
	}
187
	
188
	
189
	/**
190
	 * получение мапперов в порядке их использования с учетом вложенности
191
	 * @return array
192
	 */
193
	protected function createListRelation(){
194
		$rel_list = [];
195
		
196
		$rel_map = $this->getRelations();
197
		
198
		$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...
199
		
200
		return $rel_list;
201
	}
202
	
203
	/**
204
	 * Выстроивает порядок использования мапперов в иерархии
205
	 * @param array $rel_map
206
	 * @param type $rel_list
207
	 */
208
	protected function createListRelationReq(array $rel_map,&$rel_list,$obj_parent_link = null) {
209
		
210
		foreach ($rel_map as $rel){
211
			
212
			$obj_link = '#'.$rel['alias'].'()';
213
			
214
			if(count($rel['relations'])>0){
215
				$this->createListRelationReq($rel['relations'],$rel_list,$obj_parent_link.'get'.$rel['alias'].'()');
216
				$rel_list [$obj_parent_link.$obj_link]= $rel['name'];
217
			}
218
			else{
219
				$rel_list [$obj_parent_link.$obj_link] = $rel['name'];
220
			}
221
		}
222
	}
223
224
	
225
	/**
226
	 * получить связи
227
	 */
228
	protected function getRelations(){
229
		$rel_map = [];
230
		foreach ($this->mapping_fields as $field => $cfg){
231
			if(isset($cfg['relation'])){		
232
				$rels = $this->DI->get($cfg['relation'])->getRelations();
233
				$rel_map []= [
234
					'name'		=>	$cfg['relation'],
235
					'alias'		=>	$field,
236
					'relations'	=>	$rels
237
				];
238
			}
239
		}
240
		return $rel_map;
241
	}
242
243
	
244
245
246
247
248
249
	/**
250
	 * Подготавливаем конечный вариант Сущности
251
	 * 
252
	 * @param \Core\Infrastructure\EntityInterface $Entity
253
	 * @param array $row
254
	 * @return \Core\Infrastructure\EntityInterface
255
	 * @throws BadMethodCallException
256
	 */
257
	protected function buildEntity(EntityInterface $Entity, array $row){
258
	//ed($row);
259
        foreach ($this->mapping_fields as $alias => $cfg ) {
260
			
261
			$value = false;
262
			
263
			//автоопределени формата массива
264
			$field = $cfg['field'];
265
			
266
			$method_set = 'set' . ucfirst($alias);
267
			
268
			if(!method_exists($Entity, $method_set )){
269
				throw new BadMethodCallException("Метод {$method_set}  не определен");
270
			}			
271
			
272
			//событие на формирование поля
273
			if( isset($cfg['build']) && is_object($cfg['build']) ){
274
				
275
//				ed([
0 ignored issues
show
Unused Code Comprehensibility introduced by
53% 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...
276
//					get_class($Entity),
277
//					$row
278
//				]);
279
		
280
				
281
				$value = call_user_func($cfg['build'], $row);
282
			}
283
			//на связь
284
			elseif(isset($cfg['relation'])){
285
				
286
				$mapper = $this->DI->get($cfg['relation']);
287
				
288
				if($this->use_joins===true || empty($row[$field])){
289
					
290
					$value = $mapper->createEntity($row);
291
					
292
//					ed([
0 ignored issues
show
Unused Code Comprehensibility introduced by
53% 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...
293
//					get_class($mapper),
294
//						$row,
295
//						$value
296
//					]);
297
298
				}
299
				else{
300
					$fkey = isset($cfg['on']) ? $cfg['on'] :$mapper->key;
301
					$value = $mapper->findBySpecification((new Specification)->setWhere($fkey, $row[$field]));
302
				}				
303
				
304
			}
305
			elseif(is_string($field) && isset($row[strtolower($field)])){				
306
				$value = $row[strtolower($field)];
307
			}
308
						
309
			if($value!==false)
310
				$Entity->{$method_set}($value);
311
			
312
        }
313
				
314
        return $Entity;		
315
	}	
316
317
	
318
	/**
319
	 * из объекта формирует массив
320
	 * @param \Core\Infrastructure\EntityInterface $Entity
321
	 * @return \Core\Infrastructure\EntityInterface
322
	 * @throws BadMethodCallException
323
	 */
324
	protected function unbuildEntity(EntityInterface $Entity){
325
		
326
		$row = [];
327
328
        foreach ($this->mapping_fields as $alias => $cfg ) {
329
			
330
			//автоопределени формата массива
0 ignored issues
show
Unused Code Comprehensibility introduced by
52% 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...
331
//			if(isset($row[$this->key])){
332
//				$field = $cfg['field'];
333
//			}
334
//			else{
335
//				$field = $alias;
336
//			}			
337
			$field = $cfg['field'];
338
			
339
			$method_get = 'get' . ucfirst($alias);
340
			
341
			if(!method_exists($Entity, $method_get )){
342
				throw new BadMethodCallException("Метод {$method_get}  не определен");
343
			}		
344
			
345
			//--------------------------------------------------------------------
346
			if( isset($cfg['unbuild']) && is_object($cfg['unbuild']) ){
347
				$value = call_user_func($cfg['unbuild'], $Entity->{$method_get}() );
348
			}
349
			elseif(isset($cfg['relation']) && is_object($Entity->{$method_get}()) ){
350
				
351
				if(isset($cfg['on']))
352
					$fkey = $this->DI->get($cfg['relation'])->getFieldAlias($cfg['on']);
353
				else
354
					$fkey = 'id';
355
				
356
				$value = $Entity->{$method_get}()->{'get'.$fkey}();
357
				
358
			}			
359
			else{
360
				$value = $Entity->{$method_get}();
361
			}			
362
						
363
			$row[$field] = $value;
364
365
        }
366
			
367
        return $row;		
368
	}	
369
	
370
	/**
371
	 * Установка поля для маппинга
372
	 */
373 5
	protected function addMappingField($alias,$field = null){
374
		
375 5
		if(is_string($field)){
376 1
			$field = ['field'	=>	$field];
377 1
		}
378 4
		elseif( (is_array($field) && !isset($field['field'])) || empty($field)){
379 1
			$field['field']	= $alias;
380 1
		}
381
	
382 5
		$this->mapping_fields[$alias] = $field;
383
384 5
		if(isset($field['primary']) && $field['primary']===true){
385 1
			$this->key = $field['field'];
386 1
		}
387
		
388 5
		if(isset($field['softdelete']) && $field['softdelete']===true){
389 1
			$this->soft_delete_key = $field['field'];
390 1
		}
391
		
392 5
		$this->mapping_fields_aliases[$field['field']] = $alias;
393
		
394 5
		return $this;
395
	}	
396
	
397
398
	
399
	/**
400
	 * Установка ключа
401
	 */
402 1
	public function getPrimaryKey() {
403 1
		return $this->key;
404
	}	
405
	
406
	/**
407
	 * Устанвка поля для мягкого удаляения
408
	 */
409 1
	protected function setSoftDeleteKey() {
410 1
		return $this->soft_delete_key;
411
	}
412
413
414
	/**
415
	 * получение алиаса поля
416
	 * @param type $field
417
	 * @return type
418
	 */
419 1
	public function getFieldAlias($field){
420 1
		return $this->mapping_fields_aliases[$field];		
421
	}	
422
	
423
	/**
424
	 * получение поля по алиасу
425
	 * @param type $alias
426
	 * @return type
427
	 */
428
	public function getAliasField($alias)
429
	{
430
		return $this->mapping_fields[$alias]['field'];
431
	}
432
	
433
	
434
	/**
435
	 * 
436
	 * @param ISpecificationCriteria $specification
437
	 * @return type
438
	 */
439
	public function findBySpecification(ISpecificationCriteria $specification){
440
441
		//псеводо удаление
442
		$this->setSoftDelete($specification);
443
		
444
		$this->setRelations($specification);
445
		
446
		$specification->setLimit(1);
447
		
448
		//получение записей
449
		$res = $this->getAdapter()->getResultQuery($this->getEntityTable(),$specification);
450
451
        if (!$row = $res->row_array()) {
452
            return null;
453
        }
454
		
455
		if($this->result_array===true){
456
			return $row;
457
		}
458
		
459
		return $this->createEntity($row);	
460
		
461
	}
462
	
463
	/**
464
	 * Удаление записи
465
	 * @param EntityInterface $Entity
466
	 * @return boolean
467
	 */
468
	public function delete(EntityInterface $Entity)
469
	{
470
		$result = false;
471
		
472
		$delete_key = $this->setSoftDeleteKey();
473
		
474
		if (
475
				$delete_key > '' && 
476
				$Entity->getId() > 0){
477
				$result = $this->getAdapter()->update($this->getEntityTable(), [ $delete_key => 1 ], "{$this->getPrimaryKey()} = '{$Entity->getId()}'");
478
		}
479
		elseif($Entity->getId() > 0){
480
			
481
			if($result = $this->getAdapter()->delete($this->getEntityTable(), $this->getPrimaryKey()."  = ".$Entity->getId())){
482
				if(method_exists($this, 'onBeforeDelete' )){ $result = $this->onBeforeDelete( $Entity );}
483
			}
484
		}
485
		
486
		return $result;
487
	}
488
489
	public function findAllBySpecification(ISpecificationCriteria $specification)
490
	{
491
492
		$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...
493
		
494
		//псеводо удаление
495
		$this->setSoftDelete($specification);		
496
		
497
		$this->setRelations($specification);
498
		
499
		$res = $this->getAdapter()->getResultQuery($this->getEntityTable(),$specification);
500
		
501
		if (!$rows = $res->result_array()) {
502
            return null;
503
        }	
504
		
505
		if($this->result_array===true){
506
			return $rows;
507
		}		
508
		
509
		foreach($rows as $k =>  $row){
510
			$rows[$k] = $this->createEntity($row);
511
		}
512
		
513
		return $rows;		
514
	}
515
516
	public function findAll()
517
	{
518
		return $this->findAllBySpecification(new Specification());
519
	}
520
	
521
	/**
522
	 * Выборка удаленных моделей
523
	 * @param ISpecificationCriteria $specification
524
	 */
525
	protected function setSoftDelete(ISpecificationCriteria $specification){
526
		if(
527
				$this->use_delete === false &&
528
				$this->setSoftDeleteKey()>'' 
529
				&& !isset($specification->getWhere()[$this->setSoftDeleteKey()])
530
				)
531
		$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...
532
	}
533
	
534
	/**
535
	 * Построение join-ов
536
	 * 
537
	 * @todo добавить типы связей 
538
	 * has_many - один к многим (пост и коммеентарии)
539
	 * belongs_to - многие к многим (пользователь имет множество оплат одного заказа)
540
	 * has_one - один к одному
541
	 */
542
	protected function setRelations(ISpecificationCriteria $Specification){
543
544
		$joins = [];
545
546
		foreach ($this->mapping_fields as $field => $cfg){
547
			if(isset($cfg['relation'])){
548
				
549
				$reltype = isset($cfg['reltype']) ? $cfg['reltype'] : 'belongs_to';
550
				
551
				$this->relations[$field] = [
552
					'mapper'	=>	$mapper = $this->DI->get($cfg['relation']),
553
					'reltype'	=>  $reltype
554
				];
555
556
				$table = $mapper->getEntityTable();
557
558
				$relation_key = isset($cfg['on'])? $cfg['on'] : $mapper->key;
559
				
560
				$joins[$table] = [
561
						'alias'	=> $field,
562
						'type'	=> $reltype != 'has_many' ? 'INNER' : 'LEFT OUTER',
563
						'on'	=> "`{$this->table}`.{$cfg['field']} = `{$field}`.{$relation_key}"
564
				];
565
566
			}
567
		}	
568
569
		if($this->use_joins===true){
570
			$Specification->setJoins($joins);
571
		}			
572
	}
573
	
574
	/**
575
	 * Использование join-ов
576
	 */
577
	public function useJoins()
578
	{
579
		$o = clone $this;
580
		$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...
581
		return $o;
582
	}
583
	
584
	public function withDelete(){
585
		$o = clone $this;
586
		$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...
587
		return $o;
588
	}
589
	
590
	/**
591
	 * Данные только в массиве
592
	 * @return \SimpleORM\AbstractDataMapper
593
	 */
594
	public function resultArray(){
595
		$o = clone $this;
596
		$o->result_array = 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 $result_array.

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...
597
		return $o;
598
	}	
599
	
600
}
601