Passed
Push — master ( 252c51...fbb2a0 )
by dima
06:40
created

AbstractDataMapper::setRelations()   B

Complexity

Conditions 5
Paths 8

Size

Total Lines 26
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 30

Importance

Changes 2
Bugs 0 Features 0
Metric Value
c 2
b 0
f 0
dl 0
loc 26
ccs 0
cts 17
cp 0
rs 8.439
cc 5
eloc 12
nc 8
nop 1
crap 30
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
    public function findById($id)
113
    {
114
		$Criteria = (new Specification())->setWhere($this->key , $id);
115
		
116
        return $this->findBySpecification($Criteria);
117
    }	
118
	
119
	/**
120
	 * Cохранение сущности
121
	 * @param EntityInterface $Entity
122
	 */
123
	public function save(EntityInterface $Entity)
124
	{
125
		
126
		$data = $this->unbuildEntity($Entity);
127
		
128
		$id = $data[$this->getPrimaryKey()];
129
		unset($data[$this->getPrimaryKey()]);
130
		
131
		//insert
132
		if (empty($id)) {
133
			
134
			unset($data[$this->setSoftDeleteKey()]);
135
			
136
			$this->db->insert($this->getEntityTable(),$data);
0 ignored issues
show
Bug introduced by
The property db 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...
137
			
138
			if (!$id = $this->db->insert_id()) {
139
				return false;
140
			}
141
			
142
			$Entity->setId($id);
143
		}
144
		//update
145
		else {
146
			
147
			if(!$this->getAdapter()->update($this->getEntityTable(), $data, "{$this->getPrimaryKey()} = '{$id}'")){
148
				return false;
149
			}
150
151
		}		
152
		
153
		if(method_exists($this, 'onSaveSuccess' )){ return $this->onSaveSuccess( $Entity );}
154
		
155
		return true;
156
	}
157
	
158
	/**
159
	 * На успешное сохранение
160
	 * @param \SimpleORM\EntityInterface $Entity
161
	 */
162 View Code Duplication
	protected function onSaveSuccess(EntityInterface $Entity){
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...
163
		
164
		
165
		foreach ($this->relations as $alias => $mapper) {
166
			$Entity = $Entity->{'get'.$alias}();
167
			if(!$mapper->save($Entity)){
168
				throw new \Autoprice\Exceptions\EntityNotSaveException('Unable to save Entity!');
169
			}
170
		}
171
		
172
		return true;
173
	}
174
	
175
	/**
176
	 * На успешное удаление
177
	 * @param \SimpleORM\EntityInterface $Entity
178
	 */
179 View Code Duplication
	protected function onDeleteSuccess(EntityInterface $Entity) {
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...
180
		foreach ($this->relations as $alias => $mapper) {
181
			$Entity = $Entity->{'get'.$alias}();
182
			if(!$mapper->delete($Entity)){
183
				throw new \Autoprice\Exceptions\EntityNotDeleteException('Unable to delete Entity!');
184
			}
185
		}
186
		
187
		return true;
188
	}
189
190
191
192
	/**
193
	 * Подготавливаем конечный вариант Сущности
194
	 * 
195
	 * @param \Core\Infrastructure\EntityInterface $Entity
196
	 * @param array $row
197
	 * @return \Core\Infrastructure\EntityInterface
198
	 * @throws BadMethodCallException
199
	 */
200
	protected function buildEntity(EntityInterface $Entity, array $row){
201
		
202
        foreach ($this->mapping_fields as $alias => $cfg ) {
203
			
204
			$value = false;
205
			
206
			$field = $cfg['field'];
207
			
208
			$method_set = 'set' . ucfirst($alias);
209
			
210
			if(!method_exists($Entity, $method_set )){
211
				throw new BadMethodCallException("Метод {$method_set}  не определен");
212
			}			
213
			
214
			//событие на формирование поля
215
			if( isset($cfg['build']) && is_object($cfg['build']) ){
216
				$value = call_user_func($cfg['build'], $row);
217
			}
218
			//на связь
219
			elseif(isset($cfg['relation'])){
220
				
221
				$mapper = $this->DI->get($cfg['relation']);
222
				
223
				if($this->use_joins===true){
224
					$value = $mapper->createEntity($row);
225
				}
226
				else{
227
					$fkey = isset($cfg['on']) ? $cfg['on'] :$mapper->key;
228
					$value = $mapper->findBySpecification((new Specification)->setWhere($fkey, $row[$field]));
229
				}				
230
				
231
			}
232
			elseif(is_string($field) && isset($row[strtolower($field)])){
233
				$value = $row[strtolower($field)];
234
			}
235
			
236
			if($value!==false)
237
				$Entity->{$method_set}($value);
238
			
239
        }
240
		
241
        return $Entity;		
242
	}	
243
244
	
245
	/**
246
	 * из объекта формирует массив
247
	 * @param \Core\Infrastructure\EntityInterface $Entity
248
	 * @return \Core\Infrastructure\EntityInterface
249
	 * @throws BadMethodCallException
250
	 */
251
	protected function unbuildEntity(EntityInterface $Entity){
252
		
253
		$row = [];
254
255
        foreach ($this->mapping_fields as $alias => $cfg ) {
256
			
257
			$field = $cfg['field'];
258
			
259
			$method_get = 'get' . ucfirst($alias);
260
			
261
			if(!method_exists($Entity, $method_get )){
262
				throw new BadMethodCallException("Метод {$method_get}  не определен");
263
			}		
264
			
265
			//--------------------------------------------------------------------
266
			if( isset($cfg['unbuild']) && is_object($cfg['unbuild']) ){
267
				$value = call_user_func($cfg['unbuild'], $Entity->{$method_get}() );
268
			}
269
			elseif(isset($cfg['relation'])){
270
				
271
				if(isset($cfg['on']))
272
					$fkey = $this->DI->get($cfg['relation'])->getFieldAlias($cfg['on']);
273
				else
274
					$fkey = 'id';
275
				
276
				$value = $Entity->{$method_get}()->{'get'.$fkey}();
277
				
278
			}			
279
			else{
280
				$value = $Entity->{$method_get}();
281
			}			
282
						
283
			$row[$field] = $value;
284
285
        }
286
287
        return $row;		
288
	}	
289
	
290
	/**
291
	 * Установка поля для маппинга
292
	 */
293 5
	protected function addMappingField($alias,$field = null){
294
		
295 5
		if(is_string($field)){
296 1
			$field = ['field'	=>	$field];
297 1
		}
298 4
		elseif( (is_array($field) && !isset($field['field'])) || empty($field)){
299 1
			$field['field']	= $alias;
300 1
		}
301
	
302 5
		$this->mapping_fields[$alias] = $field;
303
304 5
		if(isset($field['primary']) && $field['primary']===true){
305 1
			$this->key = $field['field'];
306 1
		}
307
		
308 5
		if(isset($field['softdelete']) && $field['softdelete']===true){
309 1
			$this->soft_delete_key = $field['field'];
310 1
		}
311
		
312 5
		$this->mapping_fields_aliases[$field['field']] = $alias;
313
		
314 5
		return $this;
315
	}	
316
	
317
318
	
319
	/**
320
	 * Установка ключа
321
	 */
322
	protected function getPrimaryKey() {
323
		return $this->key;
324
	}	
325
	
326
	/**
327
	 * Устанвка поля для мягкого удаляения
328
	 */
329
	protected function setSoftDeleteKey() {
330
		return $this->soft_delete_key;
331
	}
332
333
334
	
335
	public function getFieldAlias($field){
336
		
337
		return $this->mapping_fields_aliases[$field];
338
		
339
	}	
340
	
341
	
342
	/**
343
	 * 
344
	 * @param ISpecificationCriteria $specification
345
	 * @return type
346
	 */
347
	public function findBySpecification(ISpecificationCriteria $specification){
348
349
		//псеводо удаление
350
		$this->setSoftDelete($specification);
351
		
352
		$this->setRelations($specification);
353
		
354
		$specification->setLimit(1);
355
		
356
		//получение записей
357
		$res = $this->getAdapter()->getResultQuery($this->getEntityTable(),$specification);
358
359
        if (!$row = $res->row_array()) {
360
            return null;
361
        }
362
        return $this->createEntity($row);				
363
	}
364
	
365
	/**
366
	 * Удаление записи
367
	 * @param EntityInterface $Entity
368
	 * @return boolean
369
	 */
370
	public function delete(EntityInterface $Entity)
371
	{
372
		$result = false;
373
		
374
		$delete_key = $this->setSoftDeleteKey();
375
		
376
		if (
377
				$delete_key > '' && 
378
				$Entity->getId() > 0){
379
				$result = $this->db->update($this->getEntityTable(), [ $delete_key => 1 ], "{$this->getPrimaryKey()} = '{$Entity->getId()}'");
380
		}
381
		elseif($Entity->getId() > 0){
382
			
383
			if($result = $this->db->delete($this->getEntityTable(), $this->getPrimaryKey()."  = ".$Entity->getId())){
384
				if(method_exists($this, 'onDeleteSuccess' )){ $result = $this->onDeleteSuccess( $Entity );}
385
			}
386
		}
387
		
388
		return $result;
389
	}
390
391
	public function findAllBySpecification(ISpecificationCriteria $specification)
392
	{
393
394
		$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...
395
		
396
		//псеводо удаление
397
		$this->setSoftDelete($specification);		
398
		
399
		$this->setRelations($specification);
400
		
401
		$res = $this->getAdapter()->getResultQuery($this->getEntityTable(),$specification);
402
		
403
		if (!$rows = $res->result_array()) {
404
            return null;
405
        }	
406
		
407
		foreach($rows as $k =>  $row){
408
			$rows[$k] = $this->createEntity($row);
409
		}
410
		
411
		return $rows;		
412
	}
413
414
	public function findAll()
415
	{
416
		return $this->findAllBySpecification(new Specification());
417
	}
418
	
419
	/**
420
	 * Выборка удаленных моделей
421
	 * @param ISpecificationCriteria $specification
422
	 */
423
	private function setSoftDelete(ISpecificationCriteria $specification){
424
		if(
425
				$this->use_delete === false &&
426
				$this->setSoftDeleteKey()>'' 
427
				&& !isset($specification->getWhere()[$this->setSoftDeleteKey()])
428
				)
429
		$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...
430
	}
431
	
432
		/**
433
	 * Построение join-ов
434
	 */
435
	protected function setRelations(ISpecificationCriteria $Specification){
436
437
		$joins = [];
438
439
		foreach ($this->mapping_fields as $field => $cfg){
440
			if(isset($cfg['relation'])){
441
				
442
				$this->relations[$field] = $mapper = $this->DI->get($cfg['relation']);
443
444
				$table = $mapper->getEntityTable();
445
446
				$relation_key = isset($cfg['on'])? $cfg['on'] : $mapper->key;
447
				
448
				$joins[$table] = [
449
						'alias'	=> $field,
450
						//'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...
451
						'on'	=> "`{$this->table}`.{$cfg['field']} = `{$field}`.{$relation_key}"
452
				];
453
454
			}
455
		}	
456
457
		if($this->use_joins===true){
458
			$Specification->setJoins($joins);
459
		}			
460
	}	
461
	
462
	/**
463
	 * Использование join-ов
464
	 */
465
	public function useJoins()
466
	{
467
		$o = clone $this;
468
		$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...
469
		return $o;
470
	}
471
	
472
	public function withDelete(){
473
		$o = clone $this;
474
		$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...
475
		return $o;
476
	}
477
478
}
479