Passed
Push — master ( fbb2a0...5a733a )
by dima
04:06
created

AbstractDataMapper::setSoftDeleteKey()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 2
Bugs 0 Features 0
Metric Value
c 2
b 0
f 0
dl 0
loc 3
ccs 0
cts 2
cp 0
rs 10
cc 1
eloc 2
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
	 * адаптер для работы с бд
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
		//protected function onAfterSave(\SimpleORM\EntityInterface $Entity, &$data)
0 ignored issues
show
Unused Code Comprehensibility introduced by
58% 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...
129
		if(method_exists($this, 'onAfterSave' )) $this->onAfterSave( $Entity, $data );
0 ignored issues
show
Bug introduced by
The method onAfterSave() does not seem to exist on object<SimpleORM\AbstractDataMapper>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

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