Passed
Push — master ( 41bc94...c383d9 )
by dima
04:18
created

AbstractDataMapper::setRelations()   C

Complexity

Conditions 7
Paths 20

Size

Total Lines 29
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 56

Importance

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