Completed
Push — work-fleets ( 961997...006942 )
by SuperNova.WS
06:22
created

EntityModel   B

Complexity

Total Complexity 36

Size/Duplication

Total Lines 295
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 4

Test Coverage

Coverage 0%

Importance

Changes 13
Bugs 1 Features 1
Metric Value
c 13
b 1
f 1
dl 0
loc 295
ccs 0
cts 81
cp 0
rs 8.8
wmc 36
lcom 1
cbo 4

21 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 9 3
B processRow() 0 15 7
A importRow() 0 7 3
A exportRow() 0 4 1
A fromArray() 0 6 1
A buildContainer() 0 8 1
A isEmpty() 0 3 1
A isNew() 0 3 1
A load() 0 6 1
A insert() 0 4 1
A update() 0 7 1
A delete() 0 3 1
A onSaveUnchanged() 0 5 1
A onSaveNew() 0 5 1
B save() 0 17 6
A getRowOperator() 0 3 1
A setTableName() 0 3 1
A getTableName() 0 3 1
A getProperties() 0 3 1
A extendProperties() 0 3 1
A getAccessors() 0 3 1
1
<?php
2
namespace Entity;
3
4
use \Common\Accessors;
5
6
/**
7
 * Class EntityModel
8
 *
9
 * This class have only one instance - i.e. is a service
10
 * Describes persistent entity - which can be loaded from/stored to storage
11
 *
12
 *
13
 * Introduces linked models and export/import operations
14
 *
15
 * Importer is a callable like
16
 *    function ($that, &$row[, $propertyName[, $fieldName]]) {}
17
 *
18
 * Exporter is a callable like
19
 *    function ($that, &$row[, $propertyName[, $fieldName]]) {}
20
 *
21
 */
22
class EntityModel {
23
  /**
24
   * Service to work with rows
25
   *
26
   * ALL DB ACCESS SHOULD BE DONE VIA ROW OPERATOR! NO DIRECT ACCESS TO DB IS ALLOWED!
27
   *
28
   * @var \DbRowDirectOperator $rowOperator
29
   */
30
  protected $rowOperator;
31
  /**
32
   * Name of table for this entity
33
   *
34
   * @var string $tableName
35
   */
36
  protected $tableName = '_table';
37
38
  /**
39
   * Name of exception class that would be thrown
40
   *
41
   * Uses for calling when you don't know which exact exception should be called
42
   * On Entity\EntityModel's children should be used exception class name
43
   *
44
   * @var string $exceptionClass
45
   */
46
  protected $exceptionClass = 'Entity\EntityException';
47
  protected $entityContainerClass = '\Entity\EntityContainer';
48
49
  /**
50
   * Property list and description
51
   *
52
   * propertyName => array(
53
   *    P_DB_FIELD => 'dbFieldName', - directly converts property to field and vice versa
54
   * )
55
   *
56
   * @var array[] $properties
57
   */
58
  protected $properties = array();
59
60
  /**
61
   * @var Accessors $accessors
62
   */
63
  protected $accessors;
64
65
  protected $newProperties = array();
66
67
  /**
68
   * Entity\EntityModel constructor.
69
   *
70
   * @param \Common\GlobalContainer $gc
71
   */
72
  public function __construct($gc) {
73
    // Here own rowOperator can be made - if needed to operate other, non-global, DB
74
    $this->rowOperator = $gc->dbGlobalRowOperator;
0 ignored issues
show
Documentation Bug introduced by
It seems like $gc->dbGlobalRowOperator can also be of type object<Closure>. However, the property $rowOperator is declared as type object<DbRowDirectOperator>. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
75
    $this->accessors = new Accessors();
76
77
    if (property_exists($this, 'newProperties') && !empty($this->newProperties)) {
78
      $this->extendProperties($this->newProperties);
79
    }
80
  }
81
82
  /**
83
   * @param EntityContainer $that
84
   * @param string          $accessor
85
   */
86
  protected function processRow($that, $accessor) {
87
    foreach ($this->properties as $propertyName => $propertyData) {
88
      $fieldName = !empty($propertyData[P_DB_FIELD]) ? $propertyData[P_DB_FIELD] : '';
89
      if ($this->accessors->exists($accessor, $propertyName)) {
90
        $this->accessors->execute($accessor, $propertyName, array($that, $propertyName, $fieldName));
91
      } elseif ($fieldName) {
92
        if ($accessor == P_CONTAINER_IMPORT) {
93
          $that->$propertyName = isset($that->row[$fieldName]) ? $that->row[$fieldName] : null;
94
        } else {
95
          $that->row += array($fieldName => $that->$propertyName);
96
        }
97
      }
98
      // Otherwise it's internal field - filled and used internally
99
    }
100
  }
101
102
  /**
103
   * Import DB row state into object properties
104
   *
105
   * @param EntityContainer $cEntity
106
   * @param array           $row
107
   */
108
  public function importRow($cEntity, $row) {
109
    $cEntity->clear();
110
    if (is_array($row) && !empty($row)) {
111
      $cEntity->row = $row;
112
      $this->processRow($cEntity, P_CONTAINER_IMPORT);
113
    }
114
  }
115
116
  /**
117
   * Exports object properties to DB row state with ID
118
   *
119
   * @param EntityContainer $cEntity
120
   */
121
  public function exportRow($cEntity) {
122
    $cEntity->row = array();
123
    $this->processRow($cEntity, P_CONTAINER_EXPORT);
124
  }
125
126
  /**
127
   * @param array $array
128
   *
129
   * @return EntityContainer
130
   */
131
  public function fromArray($array) {
132
    $cEntity = $this->buildContainer();
133
    $this->importRow($cEntity, $array);
134
135
    return $cEntity;
136
  }
137
138
  /**
139
   * @return EntityContainer
140
   */
141
  public function buildContainer() {
142
    /**
143
     * @var EntityContainer $container
144
     */
145
    $container = new $this->entityContainerClass($this);
146
147
    return $container;
148
  }
149
150
  /**
151
   * @param EntityContainer $cEntity
152
   *
153
   * @return bool
154
   */
155
  public function isEmpty($cEntity) {
156
    return $cEntity->isEmpty();
157
  }
158
159
  /**
160
   * @param EntityContainer $cEntity
161
   *
162
   * @return bool
163
   */
164
  // TODO - Loaded flag ?????
0 ignored issues
show
Unused Code Comprehensibility introduced by
36% 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...
165
  public function isNew($cEntity) {
166
    return $cEntity->isEmpty();
167
  }
168
169
  //
170
  // Save/load methods =================================================================================================
171
172
  /**
173
   * @param array $filter
174
   *
175
   * @return EntityContainer|false
176
   */
177
  public function load($filter) {
0 ignored issues
show
Unused Code introduced by
The parameter $filter is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
178
    $cEntity = false;
0 ignored issues
show
Unused Code introduced by
$cEntity 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...
179
    $cEntity = $this->buildContainer();
180
181
    return $cEntity;
182
  }
183
184
  /**
185
   * @param EntityContainer $cEntity
186
   */
187
  protected function insert($cEntity) {
188
    $this->rowOperator->insert($this, $this->exportRow($cEntity));
0 ignored issues
show
Documentation introduced by
$this->exportRow($cEntity) is of type null, but the function expects a array.

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...
189
    // TODO - re-read record
190
  }
191
192
  /**
193
   * @param EntityContainer $cEntity
194
   *
195
   * @throws \Exception
196
   */
197
  protected function update($cEntity) {
0 ignored issues
show
Unused Code introduced by
The parameter $cEntity is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
198
    // TODO - separate real changes from internal ones
199
    // Generate changeset row
200
    // Foreach all rows. If there is change and no delta - then put delta. Otherwise put change
201
    // If row not empty - update
202
    throw new \Exception(__CLASS__ . '::update() in ' . get_called_class() . 'is not yet implemented');
203
  }
204
205
  /**
206
   * @param EntityContainer $cEntity
207
   *
208
   * @throws \Exception
209
   */
210
  protected function delete($cEntity) {
211
    throw new \Exception(__CLASS__ . '::delete() in ' . get_called_class() . 'is not yet implemented');
212
  }
213
214
  /**
215
   * Method is called when trying to save DB_RECORD_LOADED but unchanged container
216
   *
217
   * Generally in this case no need in DB operations
218
   * If any entity require to save empty data (for updating timestamp for ex.) it should override this method
219
   *
220
   * @param EntityContainer $cEntity
221
   *
222
   * @throws \Exception
223
   */
224
  protected function onSaveUnchanged($cEntity) {
0 ignored issues
show
Unused Code introduced by
The parameter $cEntity is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
225
    // TODO - or just save nothing ?????
226
//    throw new \Exception('EntityModel isNotEmpty, have dbId and not CHANGED! It can\'t be!');
227
    throw new \Exception(__CLASS__ . '::unchanged() in ' . get_called_class() . 'is not yet implemented');
228
  }
229
230
  /**
231
   * Method is called when trying to save newly created DB_RECORD_NEW and Empty container
232
   *
233
   * If it is needed to really save empty container (for log purposes, for ex.) child should override this method
234
   *
235
   * @param EntityContainer $cEntity
236
   *
237
   * @throws \Exception
238
   */
239
  protected function onSaveNew($cEntity) {
0 ignored issues
show
Unused Code introduced by
The parameter $cEntity is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
240
    // Just created container and doesn't use it
241
//    throw new \Exception('EntityModel isEmpty but not loaded! It can\'t be!');
242
    throw new \Exception(__CLASS__ . '::emptyAction() in ' . get_called_class() . 'is not yet implemented');
243
  }
244
245
  /**
246
   * Saves entity to DB
247
   *
248
   * @param EntityContainer $cEntity
249
   */
250
  protected function save($cEntity) {
251
    if ($this->isEmpty($cEntity)) {
252
      if ($cEntity->isLoaded) {
0 ignored issues
show
Documentation introduced by
The property isLoaded does not exist on object<Entity\EntityContainer>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
253
        $this->delete($cEntity);
254
      } else {
255
        $this->onSaveNew($cEntity);
256
      }
257
    } else {
258
      if (empty($cEntity->dbId)) {
0 ignored issues
show
Documentation introduced by
The property dbId does not exist on object<Entity\EntityContainer>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
259
        $this->insert($cEntity);
260
      } elseif (method_exists($cEntity, 'isChanged') && $cEntity->isChanged()) {
261
        $this->update($cEntity);
262
      } else {
263
        $this->onSaveUnchanged($cEntity);
264
      }
265
    }
266
  }
267
268
269
  //
270
  // Protected properties accessors ====================================================================================
271
272
  /**
273
   * @return \DbRowDirectOperator
274
   */
275
  public function getRowOperator() {
276
    return $this->rowOperator;
277
  }
278
279
  /**
280
   * @param string $value
281
   */
282
  public function setTableName($value) {
283
    $this->tableName = $value;
284
  }
285
286
  /**
287
   * Gets entity's table name
288
   *
289
   * @return string
290
   */
291
  public function getTableName() {
292
    return $this->tableName;
293
  }
294
295
  /**
296
   * @return array[]
297
   */
298
  public function getProperties() {
299
    return $this->properties;
300
  }
301
302
  /**
303
   * @param $array
304
   */
305
  public function extendProperties($array) {
306
    $this->properties += $array;
307
  }
308
309
  /**
310
   * @return Accessors
311
   */
312
  public function getAccessors() {
313
    return $this->accessors;
314
  }
315
316
}
317