Completed
Push — master ( a8d4f8...8bb334 )
by Alexander
220:03 queued 217:16
created

DbSession::close()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 3

Importance

Changes 0
Metric Value
cc 3
eloc 3
nc 3
nop 0
dl 0
loc 6
ccs 4
cts 4
cp 1
crap 3
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * @link http://www.yiiframework.com/
4
 * @copyright Copyright (c) 2008 Yii Software LLC
5
 * @license http://www.yiiframework.com/license/
6
 */
7
8
namespace yii\web;
9
10
use Yii;
11
use yii\base\InvalidConfigException;
12
use yii\db\Connection;
13
use yii\db\PdoValue;
14
use yii\db\Query;
15
use yii\di\Instance;
16
use yii\helpers\ArrayHelper;
17
18
/**
19
 * DbSession extends [[Session]] by using database as session data storage.
20
 *
21
 * By default, DbSession stores session data in a DB table named 'session'. This table
22
 * must be pre-created. The table name can be changed by setting [[sessionTable]].
23
 *
24
 * The following example shows how you can configure the application to use DbSession:
25
 * Add the following to your application config under `components`:
26
 *
27
 * ```php
28
 * 'session' => [
29
 *     'class' => 'yii\web\DbSession',
30
 *     // 'db' => 'mydb',
31
 *     // 'sessionTable' => 'my_session',
32
 * ]
33
 * ```
34
 *
35
 * DbSession extends [[MultiFieldSession]], thus it allows saving extra fields into the [[sessionTable]].
36
 * Refer to [[MultiFieldSession]] for more details.
37
 *
38
 * @author Qiang Xue <[email protected]>
39
 * @since 2.0
40
 */
41
class DbSession extends MultiFieldSession
42
{
43
    /**
44
     * @var Connection|array|string the DB connection object or the application component ID of the DB connection.
45
     * After the DbSession object is created, if you want to change this property, you should only assign it
46
     * with a DB connection object.
47
     * Starting from version 2.0.2, this can also be a configuration array for creating the object.
48
     */
49
    public $db = 'db';
50
    /**
51
     * @var string the name of the DB table that stores the session data.
52
     * The table should be pre-created as follows:
53
     *
54
     * ```sql
55
     * CREATE TABLE session
56
     * (
57
     *     id CHAR(40) NOT NULL PRIMARY KEY,
58
     *     expire INTEGER,
59
     *     data BLOB
60
     * )
61
     * ```
62
     *
63
     * where 'BLOB' refers to the BLOB-type of your preferred DBMS. Below are the BLOB type
64
     * that can be used for some popular DBMS:
65
     *
66
     * - MySQL: LONGBLOB
67
     * - PostgreSQL: BYTEA
68
     * - MSSQL: BLOB
69
     *
70
     * When using DbSession in a production server, we recommend you create a DB index for the 'expire'
71
     * column in the session table to improve the performance.
72
     *
73
     * Note that according to the php.ini setting of `session.hash_function`, you may need to adjust
74
     * the length of the `id` column. For example, if `session.hash_function=sha256`, you should use
75
     * length 64 instead of 40.
76
     */
77
    public $sessionTable = '{{%session}}';
78
79
    /**
80
    * @var array Session fields to be written into session table columns
81
    * @since 2.0.17
82
    */
83
    protected $fields = [];
84
85
    /**
86
     * Initializes the DbSession component.
87
     * This method will initialize the [[db]] property to make sure it refers to a valid DB connection.
88
     * @throws InvalidConfigException if [[db]] is invalid.
89
     */
90 21
    public function init()
91
    {
92 21
        parent::init();
93 21
        $this->db = Instance::ensure($this->db, Connection::className());
0 ignored issues
show
Deprecated Code introduced by
The function yii\base\BaseObject::className() has been deprecated: since 2.0.14. On PHP >=5.5, use `::class` instead. ( Ignorable by Annotation )

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

93
        $this->db = Instance::ensure($this->db, /** @scrutinizer ignore-deprecated */ Connection::className());

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
94 21
    }
95
96
    /**
97
     * Updates the current session ID with a newly generated one .
98
     * Please refer to <https://secure.php.net/session_regenerate_id> for more details.
99
     * @param bool $deleteOldSession Whether to delete the old associated session file or not.
100
     */
101
    public function regenerateID($deleteOldSession = false)
102
    {
103
        $oldID = session_id();
104
105
        // if no session is started, there is nothing to regenerate
106
        if (empty($oldID)) {
107
            return;
108
        }
109
110
        parent::regenerateID(false);
111
        $newID = session_id();
112
        // if session id regeneration failed, no need to create/update it.
113
        if (empty($newID)) {
114
            Yii::warning('Failed to generate new session ID', __METHOD__);
115
            return;
116
        }
117
118
        $row = $this->db->useMaster(function() use ($oldID) {
119
            return (new Query())->from($this->sessionTable)
120
               ->where(['id' => $oldID])
121
               ->createCommand($this->db)
122
               ->queryOne();
123
        });
124
125
        if ($row !== false) {
126
            if ($deleteOldSession) {
127
                $this->db->createCommand()
128
                    ->update($this->sessionTable, ['id' => $newID], ['id' => $oldID])
129
                    ->execute();
130
            } else {
131
                $row['id'] = $newID;
132
                $this->db->createCommand()
133
                    ->insert($this->sessionTable, $row)
134
                    ->execute();
135
            }
136
        } else {
137
            // shouldn't reach here normally
138
            $this->db->createCommand()
139
                ->insert($this->sessionTable, $this->composeFields($newID, ''))
140
                ->execute();
141
        }
142
    }
143
144
    /**
145
     * Ends the current session and store session data.
146
     * @since 2.0.17
147
     */
148 6
    public function close()
149
    {
150 6
        if ($this->getIsActive()) {
151
            // prepare writeCallback fields before session closes
152 3
            $this->fields = $this->composeFields();
153 3
            YII_DEBUG ? session_write_close() : @session_write_close();
0 ignored issues
show
Bug introduced by
Are you sure the usage of session_write_close() is correct as it seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
154
        }
155 6
    }
156
157
    /**
158
     * Session read handler.
159
     * @internal Do not call this method directly.
160
     * @param string $id session ID
161
     * @return string the session data
162
     */
163 18
    public function readSession($id)
164
    {
165 18
        $query = new Query();
166 18
        $query->from($this->sessionTable)
167 18
            ->where('[[expire]]>:expire AND [[id]]=:id', [':expire' => time(), ':id' => $id]);
168
169 18
        if ($this->readCallback !== null) {
170
            $fields = $query->one($this->db);
171
            return $fields === false ? '' : $this->extractData($fields);
172
        }
173
174 18
        $data = $query->select(['data'])->scalar($this->db);
175 18
        return $data === false ? '' : $data;
176
    }
177
178
    /**
179
     * Session write handler.
180
     * @internal Do not call this method directly.
181
     * @param string $id session ID
182
     * @param string $data session data
183
     * @return bool whether session write is successful
184
     */
185 18
    public function writeSession($id, $data)
186
    {
187
        // exception must be caught in session write handler
188
        // https://secure.php.net/manual/en/function.session-set-save-handler.php#refsect1-function.session-set-save-handler-notes
189
        try {
190
            // ensure backwards compatability (fixed #9438)
191 18
            if ($this->writeCallback && !$this->fields) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->fields of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
192 3
                $this->fields = $this->composeFields();
193
            }
194
            // ensure data consistency
195 18
            if (!isset($this->fields['data'])) {
196 15
                $this->fields['data'] = $data;
197
            } else {
198 3
                $_SESSION = $this->fields['data'];
199
            }
200
            // ensure 'id' and 'expire' are never affected by [[writeCallback]]
201 18
            $this->fields = array_merge($this->fields, [
202 18
                'id' => $id,
203 18
                'expire' => time() + $this->getTimeout(),
204
            ]);
205 18
            $this->fields = $this->typecastFields($this->fields);
206 18
            $this->db->createCommand()->upsert($this->sessionTable, $this->fields)->execute();
207 18
            $this->fields = [];
208
        } catch (\Exception $e) {
209
            Yii::$app->errorHandler->handleException($e);
210
            return false;
211
        }
212 18
        return true;
213
    }
214
215
    /**
216
     * Session destroy handler.
217
     * @internal Do not call this method directly.
218
     * @param string $id session ID
219
     * @return bool whether session is destroyed successfully
220
     */
221 6
    public function destroySession($id)
222
    {
223 6
        $this->db->createCommand()
224 6
            ->delete($this->sessionTable, ['id' => $id])
225 6
            ->execute();
226
227 6
        return true;
228
    }
229
230
    /**
231
     * Session GC (garbage collection) handler.
232
     * @internal Do not call this method directly.
233
     * @param int $maxLifetime the number of seconds after which data will be seen as 'garbage' and cleaned up.
234
     * @return bool whether session is GCed successfully
235
     */
236 6
    public function gcSession($maxLifetime)
237
    {
238 6
        $this->db->createCommand()
239 6
            ->delete($this->sessionTable, '[[expire]]<:expire', [':expire' => time()])
240 6
            ->execute();
241
242 6
        return true;
243
    }
244
245
    /**
246
     * Method typecasts $fields before passing them to PDO.
247
     * Default implementation casts field `data` to `\PDO::PARAM_LOB`.
248
     * You can override this method in case you need special type casting.
249
     *
250
     * @param array $fields Fields, that will be passed to PDO. Key - name, Value - value
251
     * @return array
252
     * @since 2.0.13
253
     */
254 18
    protected function typecastFields($fields)
255
    {
256 18
        if (isset($fields['data']) && !is_array($fields['data']) && !is_object($fields['data'])) {
257 18
            $fields['data'] = new PdoValue($fields['data'], \PDO::PARAM_LOB);
258
        }
259
260 18
        return $fields;
261
    }
262
}
263