Passed
Push — master ( 678d6d...a52660 )
by dima
02:36
created

AbstractDataMapper::resultArray()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 5
ccs 0
cts 4
cp 0
rs 9.4285
cc 1
eloc 4
nc 1
nop 0
crap 2
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
		
259
        foreach ($this->mapping_fields as $alias => $cfg ) {
260
			
261
			$value = false;
262
			
263
			//автоопределени формата массива
264
			if(isset($row[$this->key])){
265
				$field = $cfg['field'];
266
			}
267
			else{
268
				$field = $alias;
269
			}
270
			
271
			$method_set = 'set' . ucfirst($alias);
272
			
273
			if(!method_exists($Entity, $method_set )){
274
				throw new BadMethodCallException("Метод {$method_set}  не определен");
275
			}			
276
			
277
			//событие на формирование поля
278
			if( isset($cfg['build']) && is_object($cfg['build']) ){
279
				$value = call_user_func($cfg['build'], $row);
280
			}
281
			//на связь
282
			elseif(isset($cfg['relation'])){
283
				
284
				$mapper = $this->DI->get($cfg['relation']);
285
				
286
				if($this->use_joins===true || empty($row[$field])){
287
					$value = $mapper->createEntity($row);
288
				}
289
				else{
290
					$fkey = isset($cfg['on']) ? $cfg['on'] :$mapper->key;
291
					$value = $mapper->findBySpecification((new Specification)->setWhere($fkey, $row[$field]));
292
				}				
293
				
294
			}
295
			elseif(is_string($field) && isset($row[strtolower($field)])){				
296
				$value = $row[strtolower($field)];
297
			}
298
						
299
			if($value!==false)
300
				$Entity->{$method_set}($value);
301
			
302
        }
303
				
304
        return $Entity;		
305
	}	
306
307
	
308
	/**
309
	 * из объекта формирует массив
310
	 * @param \Core\Infrastructure\EntityInterface $Entity
311
	 * @return \Core\Infrastructure\EntityInterface
312
	 * @throws BadMethodCallException
313
	 */
314
	protected function unbuildEntity(EntityInterface $Entity){
315
		
316
		$row = [];
317
318
        foreach ($this->mapping_fields as $alias => $cfg ) {
319
			
320
			$field = $cfg['field'];
321
			
322
			$method_get = 'get' . ucfirst($alias);
323
			
324
			if(!method_exists($Entity, $method_get )){
325
				throw new BadMethodCallException("Метод {$method_get}  не определен");
326
			}		
327
			
328
			//--------------------------------------------------------------------
329
			if( isset($cfg['unbuild']) && is_object($cfg['unbuild']) ){
330
				$value = call_user_func($cfg['unbuild'], $Entity->{$method_get}() );
331
			}
332
			elseif(isset($cfg['relation']) && is_object($Entity->{$method_get}()) ){
333
				
334
				if(isset($cfg['on']))
335
					$fkey = $this->DI->get($cfg['relation'])->getFieldAlias($cfg['on']);
336
				else
337
					$fkey = 'id';
338
				
339
				$value = $Entity->{$method_get}()->{'get'.$fkey}();
340
				
341
			}			
342
			else{
343
				$value = $Entity->{$method_get}();
344
			}			
345
						
346
			$row[$field] = $value;
347
348
        }
349
			
350
        return $row;		
351
	}	
352
	
353
	/**
354
	 * Установка поля для маппинга
355
	 */
356 5
	protected function addMappingField($alias,$field = null){
357
		
358 5
		if(is_string($field)){
359 1
			$field = ['field'	=>	$field];
360 1
		}
361 4
		elseif( (is_array($field) && !isset($field['field'])) || empty($field)){
362 1
			$field['field']	= $alias;
363 1
		}
364
	
365 5
		$this->mapping_fields[$alias] = $field;
366
367 5
		if(isset($field['primary']) && $field['primary']===true){
368 1
			$this->key = $field['field'];
369 1
		}
370
		
371 5
		if(isset($field['softdelete']) && $field['softdelete']===true){
372 1
			$this->soft_delete_key = $field['field'];
373 1
		}
374
		
375 5
		$this->mapping_fields_aliases[$field['field']] = $alias;
376
		
377 5
		return $this;
378
	}	
379
	
380
381
	
382
	/**
383
	 * Установка ключа
384
	 */
385 1
	public function getPrimaryKey() {
386 1
		return $this->key;
387
	}	
388
	
389
	/**
390
	 * Устанвка поля для мягкого удаляения
391
	 */
392 1
	protected function setSoftDeleteKey() {
393 1
		return $this->soft_delete_key;
394
	}
395
396
397
	/**
398
	 * получение алиаса поля
399
	 * @param type $field
400
	 * @return type
401
	 */
402 1
	public function getFieldAlias($field){
403 1
		return $this->mapping_fields_aliases[$field];		
404
	}	
405
	
406
	/**
407
	 * получение поля по алиасу
408
	 * @param type $alias
409
	 * @return type
410
	 */
411
	public function getAliasField($alias)
412
	{
413
		return $this->mapping_fields[$alias]['field'];
414
	}
415
	
416
	
417
	/**
418
	 * 
419
	 * @param ISpecificationCriteria $specification
420
	 * @return type
421
	 */
422
	public function findBySpecification(ISpecificationCriteria $specification){
423
424
		//псеводо удаление
425
		$this->setSoftDelete($specification);
426
		
427
		$this->setRelations($specification);
428
		
429
		$specification->setLimit(1);
430
		
431
		//получение записей
432
		$res = $this->getAdapter()->getResultQuery($this->getEntityTable(),$specification);
433
434
        if (!$row = $res->row_array()) {
435
            return null;
436
        }
437
		
438
		if($this->result_array===true){
439
			return $row;
440
		}
441
		
442
		return $this->createEntity($row);	
443
		
444
	}
445
	
446
	/**
447
	 * Удаление записи
448
	 * @param EntityInterface $Entity
449
	 * @return boolean
450
	 */
451
	public function delete(EntityInterface $Entity)
452
	{
453
		$result = false;
454
		
455
		$delete_key = $this->setSoftDeleteKey();
456
		
457
		if (
458
				$delete_key > '' && 
459
				$Entity->getId() > 0){
460
				$result = $this->getAdapter()->update($this->getEntityTable(), [ $delete_key => 1 ], "{$this->getPrimaryKey()} = '{$Entity->getId()}'");
461
		}
462
		elseif($Entity->getId() > 0){
463
			
464
			if($result = $this->getAdapter()->delete($this->getEntityTable(), $this->getPrimaryKey()."  = ".$Entity->getId())){
465
				if(method_exists($this, 'onBeforeDelete' )){ $result = $this->onBeforeDelete( $Entity );}
466
			}
467
		}
468
		
469
		return $result;
470
	}
471
472
	public function findAllBySpecification(ISpecificationCriteria $specification)
473
	{
474
475
		$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...
476
		
477
		//псеводо удаление
478
		$this->setSoftDelete($specification);		
479
		
480
		$this->setRelations($specification);
481
		
482
		$res = $this->getAdapter()->getResultQuery($this->getEntityTable(),$specification);
483
		
484
		if (!$rows = $res->result_array()) {
485
            return null;
486
        }	
487
		
488
		if($this->result_array===true){
489
			return $rows;
490
		}		
491
		
492
		foreach($rows as $k =>  $row){
493
			$rows[$k] = $this->createEntity($row);
494
		}
495
		
496
		return $rows;		
497
	}
498
499
	public function findAll()
500
	{
501
		return $this->findAllBySpecification(new Specification());
502
	}
503
	
504
	/**
505
	 * Выборка удаленных моделей
506
	 * @param ISpecificationCriteria $specification
507
	 */
508
	private function setSoftDelete(ISpecificationCriteria $specification){
509
		if(
510
				$this->use_delete === false &&
511
				$this->setSoftDeleteKey()>'' 
512
				&& !isset($specification->getWhere()[$this->setSoftDeleteKey()])
513
				)
514
		$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...
515
	}
516
	
517
	/**
518
	 * Построение join-ов
519
	 * 
520
	 * @todo добавить типы связей 
521
	 * has_many - один к многим (пост и коммеентарии)
522
	 * belongs_to - многие к многим (пользователь имет множество оплат одного заказа)
523
	 * has_one - один к одному
524
	 */
525
	protected function setRelations(ISpecificationCriteria $Specification){
526
527
		$joins = [];
528
529
		foreach ($this->mapping_fields as $field => $cfg){
530
			if(isset($cfg['relation'])){
531
				
532
				$this->relations[$field] = [
533
					'mapper'	=>	$mapper = $this->DI->get($cfg['relation']),
534
					'reltype'	=>  isset($cfg['reltype']) ? $cfg['reltype'] : 'belongs_to'
535
				];
536
537
				$table = $mapper->getEntityTable();
538
539
				$relation_key = isset($cfg['on'])? $cfg['on'] : $mapper->key;
540
				
541
				$joins[$table] = [
542
						'alias'	=> $field,
543
						'type'	=> $cfg['reltype'] != 'has_many' ? 'INNER' : 'LEFT OUTER',
544
						'on'	=> "`{$this->table}`.{$cfg['field']} = `{$field}`.{$relation_key}"
545
				];
546
547
			}
548
		}	
549
550
		if($this->use_joins===true){
551
			$Specification->setJoins($joins);
552
		}			
553
	}
554
	
555
	/**
556
	 * Использование join-ов
557
	 */
558
	public function useJoins()
559
	{
560
		$o = clone $this;
561
		$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...
562
		return $o;
563
	}
564
	
565
	public function withDelete(){
566
		$o = clone $this;
567
		$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...
568
		return $o;
569
	}
570
	
571
	/**
572
	 * Данные только в массиве
573
	 * @return \SimpleORM\AbstractDataMapper
574
	 */
575
	public function resultArray(){
576
		$o = clone $this;
577
		$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...
578
		return $o;
579
	}	
580
	
581
}
582