Passed
Push — work-fleets ( 4e1f0c...91349a )
by SuperNova.WS
10:40
created

EntityModel::update()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 2
c 1
b 0
f 0
nc 1
nop 1
dl 0
loc 7
rs 9.4285
ccs 0
cts 2
cp 0
crap 2
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
23
class EntityModel {
24
  /**
25
   * Service to work with rows
26
   *
27
   * ALL DB ACCESS SHOULD BE DONE VIA ROW OPERATOR! NO DIRECT ACCESS TO DB IS ALLOWED!
28
   *
29
   * @var \DbRowDirectOperator $rowOperator
30
   */
31
  protected $rowOperator;
32
  /**
33
   * Name of table for this entity
34
   *
35
   * @var string $tableName
36
   */
37
  protected $tableName = '_table';
38
39
  /**
40
   * Name of exception class that would be thrown
41
   *
42
   * Uses for calling when you don't know which exact exception should be called
43
   * On Entity\EntityModel's children should be used exception class name
44
   *
45
   * @var string $exceptionClass
46
   */
47
  protected $exceptionClass = 'Entity\EntityException';
48
  protected $entityContainerClass = '\Entity\EntityContainer';
49
50
  /**
51
   * Property list and description
52
   *
53
   * propertyName => array(
54
   *    P_DB_FIELD => 'dbFieldName', - directly converts property to field and vice versa
55
   * )
56
   *
57
   * @var array[] $properties
58
   */
59
  protected $properties = array();
60
61
  /**
62
   * @var Accessors $accessors
63
   */
64
  protected $accessors;
65
66
  protected $newProperties = array();
67
68
  /**
69
   * Entity\EntityModel constructor.
70
   *
71
   * @param \Common\GlobalContainer $gc
72
   */
73
  public function __construct($gc) {
74
    // Here own rowOperator can be made - if needed to operate other, non-global, DB
75
    $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...
76
    $this->accessors = new Accessors();
77
78
    if(property_exists($this, 'newProperties') && !empty($this->newProperties)) {
79
      $this->extendProperties($this->newProperties);
80
    }
81
  }
82
83
  /**
84
   * @param EntityContainer $that
85
   * @param string          $accessor
86
   */
87
  protected function processRow($that, $accessor) {
88
    foreach ($this->properties as $propertyName => $propertyData) {
89
      $fieldName = !empty($propertyData[P_DB_FIELD]) ? $propertyData[P_DB_FIELD] : '';
90
      if ($this->accessors->exists($accessor, $propertyName)) {
91
        $this->accessors->execute($accessor, $propertyName, array($that, $propertyName, $fieldName));
92
      } elseif ($fieldName) {
93
        if ($accessor == P_CONTAINER_IMPORT) {
94
          $that->$propertyName = isset($that->row[$fieldName]) ? $that->row[$fieldName] : null;
95
        } else {
96
          $that->row += array($fieldName => $that->$propertyName);
97
        }
98
      }
99
      // Otherwise it's internal field - filled and used internally
100
    }
101
  }
102
103
  /**
104
   * Import DB row state into object properties
105
   *
106
   * @param EntityContainer $cEntity
107
   * @param array           $row
108
   */
109
  public function importRow($cEntity, $row) {
110
    $cEntity->clear();
111
    if (is_array($row) && !empty($row)) {
112
      $cEntity->row = $row;
113
      $this->processRow($cEntity, P_CONTAINER_IMPORT);
114
    }
115
  }
116
117
  /**
118
   * Exports object properties to DB row state with ID
119
   *
120
   * @param EntityContainer $cEntity
121
   */
122
  public function exportRow($cEntity) {
123
    $cEntity->row = array();
124
    $this->processRow($cEntity, P_CONTAINER_EXPORT);
125
  }
126
127
128
  /**
129
   * @param array $array
130
   *
131
   * @return EntityContainer
132
   */
133
  public function fromArray($array) {
134
    $cEntity = $this->buildContainer();
135
    $this->importRow($cEntity, $array);
136
137
    return $cEntity;
138
  }
139
140
141
  /**
142
   * @return EntityContainer
143
   */
144
  public function buildContainer() {
145
    /**
146
     * @var EntityContainer $container
147
     */
148
    $container = new $this->entityContainerClass($this);
149
150
    return $container;
151
  }
152
153
154
  /**
155
   * @param EntityContainer $cEntity
156
   *
157
   * @return bool
158
   */
159
  public function isEmpty($cEntity) {
160
    return $cEntity->isEmpty();
161
  }
162
163
  /**
164
   * @param EntityContainer $cEntity
165
   *
166
   * @return bool
167
   */
168
  // 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...
169
  public function isNew($cEntity) {
170
    return $cEntity->isEmpty();
171
  }
172
173
174
  /**
175
   * @return \DbRowDirectOperator
176
   */
177
  public function getRowOperator() {
178
    return $this->rowOperator;
179
  }
180
181
  /**
182
   * @param string $value
183
   */
184
  public function setTableName($value) {
185
    $this->tableName = $value;
186
  }
187
188
  /**
189
   * Gets entity's table name
190
   *
191
   * @return string
192
   */
193
  public function getTableName() {
194
    return $this->tableName;
195
  }
196
197
  /**
198
   * @return array[]
199
   */
200
  public function getProperties() {
201
    return $this->properties;
202
  }
203
204
  /**
205
   * @param $array
206
   */
207
  public function extendProperties($array) {
208
    $this->properties += $array;
209
  }
210
211
  /**
212
   * @return Accessors
213
   */
214
  public function getAccessors() {
215
    return $this->accessors;
216
  }
217
218
  protected function delete(EntityContainer $cEntity) {
219
    throw new \Exception(__CLASS__ . '::delete() in ' . get_called_class() . 'is not yet implemented');
220
  }
221
222
  protected function insert(EntityContainer $cEntity) {
223
    $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...
224
    // TODO - re-read record
225
  }
226
227
  protected function update(EntityContainer $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...
228
    // TODO - separate real changes from internal ones
229
    // Generate changeset row
230
    // Foreach all rows. If there is change and no delta - then put delta. Otherwise put change
231
    // If row not empty - update
232
    throw new \Exception(__CLASS__ . '::update() in ' . get_called_class() . 'is not yet implemented');
233
  }
234
235
  protected function unchanged(EntityContainer $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...
236
    // TODO - or just save nothing ?????
237
//    throw new \Exception('EntityModel isNotEmpty, have dbId and not CHANGED! It can\'t be!');
238
    throw new \Exception(__CLASS__ . '::unchanged() in ' . get_called_class() . 'is not yet implemented');
239
  }
240
241
  protected function emptyAction(EntityContainer $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...
242
    // Just created container and doesn't use it
243
//    throw new \Exception('EntityModel isEmpty but not loaded! It can\'t be!');
244
    throw new \Exception(__CLASS__ . '::emptyAction() in ' . get_called_class() . 'is not yet implemented');
245
  }
246
247
  protected function save(EntityContainer $cEntity) {
248
    if ($this->isEmpty($cEntity)) {
249
      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...
250
        $this->delete($cEntity);
251
      } else {
252
        $this->emptyAction($cEntity);
253
      }
254
    } else {
255
      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...
256
        $this->insert($cEntity);
257
      } elseif (method_exists($cEntity, 'isChanged') && $cEntity->isChanged()) {
258
        $this->update($cEntity);
259
      } else {
260
        $this->unchanged($cEntity);
261
      }
262
    }
263
  }
264
265
}
266