Completed
Push — trunk ( 76e427...0e7f64 )
by SuperNova.WS
05:41
created

EntityDb::save()   B

Complexity

Conditions 5
Paths 5

Size

Total Lines 19
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 30

Importance

Changes 0
Metric Value
cc 5
eloc 7
nc 5
nop 0
dl 0
loc 19
ccs 0
cts 9
cp 0
crap 30
rs 8.8571
c 0
b 0
f 0
1
<?php
2
/**
3
 * Created by Gorlum 08.01.2018 14:46
4
 */
5
6
namespace Core;
7
8
9
use Common\Interfaces\IContainer;
10
use \DBAL\DbQuery;
11
use \DBAL\ActiveRecord;
12
use Common\Traits\TContainer;
13
use Exception;
14
use SN;
15
16
/**
17
 * Basic persistent entity class (lives in DB)
18
 *
19
 * Support direct ActiveRecord access
20
 * Support locked reads on transactions
21
 *
22
 * Represents in-game entity which have representation in DB (aka one or more connected ActiveRecords)
23
 *
24
 * @package Core
25
 *
26
 * @property int|string $id - bigint -
27
 *
28
 * @method array asArray() Extracts values as array [$propertyName => $propertyValue] (from ActiveRecord)
29
 * @method bool update() Updates DB record(s) in DB (from ActiveRecord)
30
 */
31
class EntityDb extends Entity implements IContainer {
32
  use TContainer;
33
34
  /**
35
   * @var string $_activeClass
36
   */
37
  protected $_activeClass = ''; // \\DBAL\\ActiveRecord
38
39
  /**
40
   * Name translation table
41
   *
42
   * [entityPropertyName => containerPropertyName]
43
   *
44
   * @var string[] $_containerTranslateNames
45
   */
46
  protected $_containerTranslateNames = [];
47
48
49
  /**
50
   * @var ActiveRecord $_container
51
   */
52
  protected $_container;
53
54
  protected $_isNew = true;
55
  protected $_forUpdate = DbQuery::DB_SHARED;
56
57
  /**
58
   * EntityDb constructor.
59
   */
60
  public function __construct() {
61
    if (empty($this->_activeClass)) {
62
      /** @noinspection PhpUnhandledExceptionInspection */
63
      throw new Exception("Class " . get_called_class() . " have no _activeClass property declared!");
64
    }
65
66
    $this->reset();
67
  }
68
69
  /**
70
   * @return $this
71
   */
72
  public function reset() {
73
//    unset($this->_container); // Strange thing - after this string it is not possible to set _container property again!
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...
74
    $this->_container = new $this->_activeClass();
75
76
    $this->_isNew = true;
77
    $this->_forUpdate = DbQuery::DB_SHARED;
78
79
    return $this;
80
  }
81
82
  /**
83
   * Reload Entity - if possible
84
   *
85
   * If entity is write-once - this method should decide if any real I/O would be performed
86
   *
87
   * @return static
88
   *
89
   * @throws Exception
90
   */
91
  public function reload() {
92
    // TODO - immutable entities which does not need reload
93
    $dbId = !empty($this->_container) ? $this->_container->id : 0;
94
95
    // If entity is new or no DB ID supplied - throw exception
96
    if ($this->isNew() || !$dbId) {
97
      /** @noinspection PhpUnhandledExceptionInspection */
98
      throw new Exception("Can't reload empty or new Entity: isNew = '{$this->isNew()}', dbId = '{$dbId}', className = " . get_called_class());
99
    }
100
101
    return $this->dbLoadRecord($dbId);
0 ignored issues
show
Bug introduced by
It seems like $dbId can also be of type string; however, parameter $id of Core\EntityDb::dbLoadRecord() does only seem to accept integer|double, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

101
    return $this->dbLoadRecord(/** @scrutinizer ignore-type */ $dbId);
Loading history...
102
  }
103
104
105
  /**
106
   * Is entity is new
107
   *
108
   * True for newly created and deleted entities
109
   * Code should decide this from context
110
   *
111
   * @return bool
112
   */
113
  public function isNew() {
114
    return $this->_isNew;
115
  }
116
117
  /**
118
   * Load entity from DB
119
   *
120
   * This basic class supports transaction locking
121
   *
122
   * If entity is multi-tabled - this method should care about loading and arrange all necessary information
123
   * If descendant entity is lock-sensitive - this method should issue all necessary locks (including parental locks)
124
   *
125
   * @param int|float $id
126
   *
127
   * @return $this
128
   */
129
  public function dbLoadRecord($id) {
130
    $this->reset();
131
132
    if (sn_db_transaction_check(false)) {
133
      $this->setForUpdate();
134
    }
135
136
    /** @var ActiveRecord $className */
137
    $className = $this->_activeClass;
138
    $container = $className::findById($id);
139
    if (!empty($container)) {
140
      $this->_isNew = false;
141
      $this->_container = $container;
142
    }
143
144
    return $this;
145
  }
146
147
  /**
148
   * Set "for update" flag
149
   *
150
   * @param bool $forUpdate - DbQuery::DB_FOR_UPDATE | DbQuery::DB_SHARED
151
   *
152
   * @return $this
153
   */
154
  public function setForUpdate($forUpdate = DbQuery::DB_FOR_UPDATE) {
155
    /** @var ActiveRecord $className */
156
    $className = $this->_activeClass;
157
    $className::setForUpdate($forUpdate);
158
159
    return $this;
160
  }
161
162
  /**
163
   * @return bool
164
   */
165
  public function dbUpdate() {
166
    return $this->_getContainer()->update();
167
  }
168
169
  /**
170
   * @return ActiveRecord
171
   */
172
  public function _getContainer() {
173
    return $this->_container;
174
  }
175
176
  /**
177
   * Translate entity property name to container property name
178
   *
179
   * Just a little sugar to avoid renaming all and everywhere
180
   *
181
   * @param string $name
182
   *
183
   * @return string
184
   */
185
  protected function _containerTranslatePropertyName($name) {
186
    return !empty($this->_containerTranslateNames[$name]) ? $this->_containerTranslateNames[$name] : $name;
187
  }
188
189
  /**
190
   * Saves entity to DB. Also handles updates (and in future - deletes. DELETE CURRENTLY NOT SUPPORTED!)
191
   *
192
   * @return bool
193
   * @throws \Exception
194
   */
195
  public function save() {
196
    $result = false;
197
198
    if ($this->isNew()) {
199
      // New record - INSERT
200
201
      // TODO - some checks that fleet recrod is valid itself
202
      // May be something like method isValid()?
203
204
      $result = !$this->isEmpty() && $this->_getContainer()->insert() && SN::$gc->repoV2->set($this);
205
    } elseif (!$this->isEmpty()) {
206
      // Record not new and not empty - UPDATE
207
      $result = $this->_getContainer()->update();
208
    } else {
0 ignored issues
show
Unused Code introduced by
This else statement is empty and can be removed.

This check looks for the else branches of if statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

These else branches can be removed.

if (rand(1, 6) > 3) {
print "Check failed";
} else {
    //print "Check succeeded";
}

could be turned into

if (rand(1, 6) > 3) {
    print "Check failed";
}

This is much more concise to read.

Loading history...
209
      // Record not new and empty - DELETE
210
211
    }
212
213
    return $result;
214
  }
215
216
}
217