Completed
Push — master ( 4333b9...659b3d )
by Dmitry
14:03
created

DbSession::writeSession()   A

Complexity

Conditions 2
Paths 4

Size

Total Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2.2109

Importance

Changes 0
Metric Value
dl 0
loc 15
rs 9.7666
c 0
b 0
f 0
ccs 5
cts 8
cp 0.625
cc 2
nc 4
nop 2
crap 2.2109
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
    /**
81
     * Initializes the DbSession component.
82
     * This method will initialize the [[db]] property to make sure it refers to a valid DB connection.
83
     * @throws InvalidConfigException if [[db]] is invalid.
84
     */
85 18
    public function init()
86
    {
87 18
        parent::init();
88 18
        $this->db = Instance::ensure($this->db, Connection::className());
0 ignored issues
show
Deprecated Code introduced by
The method yii\base\BaseObject::className() has been deprecated with message: since 2.0.14. On PHP >=5.5, use `::class` instead.

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

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

Loading history...
89 18
    }
90
91
    /**
92
     * Updates the current session ID with a newly generated one .
93
     * Please refer to <http://php.net/session_regenerate_id> for more details.
94
     * @param bool $deleteOldSession Whether to delete the old associated session file or not.
95
     */
96
    public function regenerateID($deleteOldSession = false)
97
    {
98
        $oldID = session_id();
99
100
        // if no session is started, there is nothing to regenerate
101
        if (empty($oldID)) {
102
            return;
103
        }
104
105
        parent::regenerateID(false);
106
        $newID = session_id();
107
        // if session id regeneration failed, no need to create/update it.
108
        if (empty($newID)) {
109
            Yii::warning('Failed to generate new session ID', __METHOD__);
110
            return;
111
        }
112
113
        $row = $this->db->useMaster(function() use ($oldID) {
114
            return (new Query())->from($this->sessionTable)
115
               ->where(['id' => $oldID])
116
               ->createCommand($this->db)
117
               ->queryOne();
118
        });
119
120
        if ($row !== false) {
121
            if ($deleteOldSession) {
122
                $this->db->createCommand()
123
                    ->update($this->sessionTable, ['id' => $newID], ['id' => $oldID])
124
                    ->execute();
125
            } else {
126
                $row['id'] = $newID;
127
                $this->db->createCommand()
128
                    ->insert($this->sessionTable, $row)
129
                    ->execute();
130
            }
131
        } else {
132
            // shouldn't reach here normally
133
            $this->db->createCommand()
134
                ->insert($this->sessionTable, $this->composeFields($newID, ''))
135
                ->execute();
136
        }
137
    }
138
139
    /**
140
     * Session read handler.
141
     * @internal Do not call this method directly.
142
     * @param string $id session ID
143
     * @return string the session data
144
     */
145 15
    public function readSession($id)
146
    {
147 15
        $query = new Query();
148 15
        $query->from($this->sessionTable)
149 15
            ->where('[[expire]]>:expire AND [[id]]=:id', [':expire' => time(), ':id' => $id]);
150
151 15
        if ($this->readCallback !== null) {
152
            $fields = $query->one($this->db);
153
            return $fields === false ? '' : $this->extractData($fields);
154
        }
155
156 15
        $data = $query->select(['data'])->scalar($this->db);
157 15
        return $data === false ? '' : $data;
158
    }
159
160
    /**
161
     * Session write handler.
162
     * @internal Do not call this method directly.
163
     * @param string $id session ID
164
     * @param string $data session data
165
     * @return bool whether session write is successful
166
     */
167 15
    public function writeSession($id, $data)
168
    {
169
        // exception must be caught in session write handler
170
        // http://us.php.net/manual/en/function.session-set-save-handler.php#refsect1-function.session-set-save-handler-notes
171
        try {
172 15
            $fields = $this->composeFields($id, $data);
173 15
            $fields = $this->typecastFields($fields);
174 15
            $this->db->createCommand()->upsert($this->sessionTable, $fields)->execute();
175
        } catch (\Exception $e) {
176
            Yii::$app->errorHandler->handleException($e);
177
            return false;
178
        }
179
180 15
        return true;
181
    }
182
183
    /**
184
     * Session destroy handler.
185
     * @internal Do not call this method directly.
186
     * @param string $id session ID
187
     * @return bool whether session is destroyed successfully
188
     */
189 6
    public function destroySession($id)
190
    {
191 6
        $this->db->createCommand()
192 6
            ->delete($this->sessionTable, ['id' => $id])
193 6
            ->execute();
194
195 6
        return true;
196
    }
197
198
    /**
199
     * Session GC (garbage collection) handler.
200
     * @internal Do not call this method directly.
201
     * @param int $maxLifetime the number of seconds after which data will be seen as 'garbage' and cleaned up.
202
     * @return bool whether session is GCed successfully
203
     */
204 3
    public function gcSession($maxLifetime)
205
    {
206 3
        $this->db->createCommand()
207 3
            ->delete($this->sessionTable, '[[expire]]<:expire', [':expire' => time()])
208 3
            ->execute();
209
210 3
        return true;
211
    }
212
213
    /**
214
     * Method typecasts $fields before passing them to PDO.
215
     * Default implementation casts field `data` to `\PDO::PARAM_LOB`.
216
     * You can override this method in case you need special type casting.
217
     *
218
     * @param array $fields Fields, that will be passed to PDO. Key - name, Value - value
219
     * @return array
220
     * @since 2.0.13
221
     */
222 15
    protected function typecastFields($fields)
223
    {
224 15
        if (isset($fields['data']) && !is_array($fields['data']) && !is_object($fields['data'])) {
225 15
            $fields['data'] = new PdoValue($fields['data'], \PDO::PARAM_LOB);
226
        }
227
228 15
        return $fields;
229
    }
230
}
231