1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace bedezign\yii2\audit\models; |
4
|
|
|
|
5
|
|
|
use bedezign\yii2\audit\Audit; |
6
|
|
|
use bedezign\yii2\audit\components\db\ActiveRecord; |
7
|
|
|
use bedezign\yii2\audit\components\Helper; |
8
|
|
|
use Yii; |
9
|
|
|
use yii\db\ActiveQuery; |
10
|
|
|
use yii\db\Expression; |
11
|
|
|
|
12
|
|
|
/** |
13
|
|
|
* AuditEntry |
14
|
|
|
* @package bedezign\yii2\audit\models |
15
|
|
|
* |
16
|
|
|
* @property int $id |
17
|
|
|
* @property string $created |
18
|
|
|
* @property float $duration |
19
|
|
|
* @property int $user_id 0 means anonymous |
20
|
|
|
* @property string $ip |
21
|
|
|
* @property string $route |
22
|
|
|
* @property int $memory_max |
23
|
|
|
* @property string $request_method |
24
|
|
|
* @property string $ajax |
25
|
|
|
* |
26
|
|
|
* @property AuditError[] $linkedErrors |
27
|
|
|
* @property AuditJavascript[] $javascripts |
28
|
|
|
* @property AuditTrail[] $trails |
29
|
|
|
* @property AuditMail[] $mails |
30
|
|
|
* @property AuditData[] $data |
31
|
|
|
*/ |
32
|
|
|
class AuditEntry extends ActiveRecord |
33
|
|
|
{ |
34
|
|
|
/** |
35
|
|
|
* @var bool |
36
|
|
|
*/ |
37
|
|
|
protected $autoSerialize = false; |
38
|
|
|
|
39
|
|
|
/** |
40
|
|
|
* @inheritdoc |
41
|
|
|
*/ |
42
|
195 |
|
public static function tableName() |
43
|
|
|
{ |
44
|
195 |
|
return '{{%audit_entry}}'; |
45
|
|
|
} |
46
|
|
|
|
47
|
|
|
/** |
48
|
|
|
* @param bool $initialise |
49
|
|
|
* @return static |
50
|
|
|
*/ |
51
|
117 |
|
public static function create($initialise = true) |
52
|
|
|
{ |
53
|
117 |
|
$entry = new static; |
54
|
|
|
if ($initialise) |
55
|
117 |
|
$entry->record(); |
56
|
|
|
|
57
|
117 |
|
return $entry; |
58
|
|
|
} |
59
|
|
|
|
60
|
|
|
/** |
61
|
|
|
* Returns all linked AuditError instances |
62
|
|
|
* (Called `linkedErrors()` to avoid confusion with the `getErrors()` method) |
63
|
|
|
* @return ActiveQuery |
64
|
|
|
*/ |
65
|
9 |
|
public function getLinkedErrors() |
66
|
|
|
{ |
67
|
9 |
|
return static::hasMany(AuditError::className(), ['entry_id' => 'id']); |
68
|
|
|
} |
69
|
|
|
|
70
|
|
|
/** |
71
|
|
|
* Returns all linked AuditTrail instances |
72
|
|
|
* @return ActiveQuery |
73
|
|
|
*/ |
74
|
9 |
|
public function getTrails() |
75
|
|
|
{ |
76
|
9 |
|
return static::hasMany(AuditTrail::className(), ['entry_id' => 'id']); |
77
|
|
|
} |
78
|
|
|
|
79
|
|
|
/** |
80
|
|
|
* Returns all linked AuditMail instances |
81
|
|
|
* @return ActiveQuery |
82
|
|
|
*/ |
83
|
9 |
|
public function getMails() |
84
|
|
|
{ |
85
|
9 |
|
return static::hasMany(AuditMail::className(), ['entry_id' => 'id']); |
86
|
|
|
} |
87
|
|
|
|
88
|
|
|
/** |
89
|
|
|
* Returns all linked AuditJavascript instances |
90
|
|
|
* @return ActiveQuery |
91
|
|
|
*/ |
92
|
9 |
|
public function getJavascripts() |
93
|
|
|
{ |
94
|
9 |
|
return static::hasMany(AuditJavascript::className(), ['entry_id' => 'id']); |
95
|
|
|
} |
96
|
|
|
|
97
|
|
|
/** |
98
|
|
|
* Returns all linked data records |
99
|
|
|
* @return ActiveQuery |
100
|
|
|
*/ |
101
|
3 |
|
public function getData() |
102
|
|
|
{ |
103
|
3 |
|
return static::hasMany(AuditData::className(), ['entry_id' => 'id'])->indexBy('type'); |
104
|
|
|
} |
105
|
|
|
|
106
|
|
|
/** |
107
|
|
|
* Writes a number of associated data records in one go. |
108
|
|
|
* @param $batchData |
109
|
|
|
* @param bool $compact |
110
|
|
|
* @throws \yii\db\Exception |
111
|
|
|
*/ |
112
|
45 |
|
public function addBatchData($batchData, $compact = true) |
113
|
|
|
{ |
114
|
45 |
|
$columns = ['entry_id', 'type', 'created', 'data']; |
115
|
45 |
|
$rows = []; |
116
|
45 |
|
$params = []; |
117
|
45 |
|
$date = date('Y-m-d H:i:s'); |
118
|
|
|
// Some database like postgres depend on the data being escaped correctly. |
119
|
|
|
// PDO can take care of this if you define the field as a LOB (Large OBject), but unfortunately Yii does threat values |
120
|
|
|
// for batch inserts the same way. This code adds a number of literals instead of the actual values |
121
|
|
|
// so that they can be bound right before insert and still get escaped correctly |
122
|
45 |
|
foreach ($batchData as $type => $data) { |
123
|
45 |
|
$param = ':data_' . str_replace('/', '_', $type); |
124
|
45 |
|
$rows[] = [$this->id, $type, $date, new Expression($param)]; |
125
|
45 |
|
$params[$param] = [Helper::serialize($data, $compact), \PDO::PARAM_LOB]; |
126
|
45 |
|
} |
127
|
45 |
|
static::getDb()->createCommand()->batchInsert(AuditData::tableName(), $columns, $rows)->bindValues($params)->execute(); |
128
|
45 |
|
} |
129
|
|
|
|
130
|
|
|
/** |
131
|
|
|
* @param $type |
132
|
|
|
* @param $data |
133
|
|
|
* @param bool|true $compact |
134
|
|
|
* @throws \yii\db\Exception |
135
|
|
|
*/ |
136
|
6 |
|
public function addData($type, $data, $compact = true) |
137
|
|
|
{ |
138
|
|
|
// Make sure to mark data as a large object so it gets escaped |
139
|
|
|
$record = [ |
140
|
6 |
|
'entry_id' => $this->id, |
141
|
6 |
|
'type' => $type, |
142
|
6 |
|
'created' => date('Y-m-d H:i:s'), |
143
|
6 |
|
'data' => [Helper::serialize($data, $compact), \PDO::PARAM_LOB], |
144
|
6 |
|
]; |
145
|
6 |
|
static::getDb()->createCommand()->insert(AuditData::tableName(), $record)->execute(); |
146
|
6 |
|
} |
147
|
|
|
|
148
|
|
|
/** |
149
|
|
|
* Records the current application state into the instance. |
150
|
|
|
*/ |
151
|
117 |
|
public function record() |
152
|
|
|
{ |
153
|
117 |
|
$app = Yii::$app; |
154
|
117 |
|
$request = $app->request; |
155
|
|
|
|
156
|
117 |
|
$this->route = $app->requestedAction ? $app->requestedAction->uniqueId : null; |
157
|
117 |
|
if ($request instanceof \yii\web\Request) { |
158
|
117 |
|
$this->user_id = Audit::getInstance()->getUserId(); |
159
|
117 |
|
$this->ip = $this->getUserIP(); |
160
|
117 |
|
$this->ajax = $request->isAjax; |
161
|
117 |
|
$this->request_method = $request->method; |
162
|
117 |
|
} else if ($request instanceof \yii\console\Request) { |
163
|
|
|
$this->request_method = 'CLI'; |
164
|
|
|
} |
165
|
|
|
|
166
|
117 |
|
$this->save(false); |
167
|
117 |
|
} |
168
|
|
|
|
169
|
|
|
/** |
170
|
|
|
* @return bool |
171
|
|
|
*/ |
172
|
75 |
|
public function finalize() |
173
|
|
|
{ |
174
|
75 |
|
$app = Yii::$app; |
175
|
75 |
|
$request = $app->request; |
176
|
|
|
|
177
|
75 |
|
if (!$this->user_id && $request instanceof \yii\web\Request) { |
178
|
75 |
|
$this->user_id = Audit::getInstance()->getUserId(); |
179
|
75 |
|
} |
180
|
|
|
|
181
|
75 |
|
$this->duration = microtime(true) - YII_BEGIN_TIME; |
182
|
75 |
|
$this->memory_max = memory_get_peak_usage(); |
183
|
75 |
|
return $this->save(false, ['duration', 'memory_max', 'user_id']); |
184
|
|
|
} |
185
|
|
|
|
186
|
|
|
/** |
187
|
|
|
* @return array |
188
|
|
|
*/ |
189
|
15 |
|
public function attributeLabels() |
190
|
|
|
{ |
191
|
|
|
return [ |
192
|
15 |
|
'id' => Yii::t('audit', 'Entry ID'), |
193
|
15 |
|
'created' => Yii::t('audit', 'Created'), |
194
|
15 |
|
'ip' => Yii::t('audit', 'IP'), |
195
|
15 |
|
'duration' => Yii::t('audit', 'Duration'), |
196
|
15 |
|
'user_id' => Yii::t('audit', 'User'), |
197
|
15 |
|
'memory_max' => Yii::t('audit', 'Memory'), |
198
|
15 |
|
'request_method' => Yii::t('audit', 'Request Method'), |
199
|
15 |
|
]; |
200
|
|
|
} |
201
|
|
|
|
202
|
|
|
/** |
203
|
|
|
* @return bool |
204
|
|
|
*/ |
205
|
|
|
public function hasRelatedData() |
206
|
|
|
{ |
207
|
|
|
if ($this->getLinkedErrors()->count()) { |
208
|
|
|
return true; |
209
|
|
|
} |
210
|
|
|
if ($this->getJavascripts()->count()) { |
211
|
|
|
return true; |
212
|
|
|
} |
213
|
|
|
if ($this->getMails()->count()) { |
214
|
|
|
return true; |
215
|
|
|
} |
216
|
|
|
if ($this->getTrails()->count()) { |
217
|
|
|
return true; |
218
|
|
|
} |
219
|
|
|
return false; |
220
|
|
|
} |
221
|
|
|
|
222
|
|
|
/** |
223
|
|
|
* @return string |
224
|
|
|
*/ |
225
|
|
|
public function getUserIP() |
226
|
|
|
{ |
227
|
|
|
if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) { |
228
|
|
|
return array_values(array_filter(explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']))); |
|
|
|
|
229
|
|
|
} |
230
|
|
|
return isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : null; |
231
|
|
|
} |
232
|
|
|
|
233
|
|
|
} |
234
|
|
|
|
If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.
Let’s take a look at an example:
Our function
my_function
expects aPost
object, and outputs the author of the post. The base classPost
returns a simple string and outputting a simple string will work just fine. However, the child classBlogPost
which is a sub-type ofPost
instead decided to return anobject
, and is therefore violating the SOLID principles. If aBlogPost
were passed tomy_function
, PHP would not complain, but ultimately fail when executing thestrtoupper
call in its body.