1<?php 2 3namespace Illuminate\Session; 4 5use Illuminate\Contracts\Auth\Guard; 6use Illuminate\Contracts\Container\Container; 7use Illuminate\Database\ConnectionInterface; 8use Illuminate\Database\QueryException; 9use Illuminate\Support\Arr; 10use Illuminate\Support\Carbon; 11use Illuminate\Support\InteractsWithTime; 12use SessionHandlerInterface; 13 14class DatabaseSessionHandler implements ExistenceAwareInterface, SessionHandlerInterface 15{ 16 use InteractsWithTime; 17 18 /** 19 * The database connection instance. 20 * 21 * @var \Illuminate\Database\ConnectionInterface 22 */ 23 protected $connection; 24 25 /** 26 * The name of the session table. 27 * 28 * @var string 29 */ 30 protected $table; 31 32 /** 33 * The number of minutes the session should be valid. 34 * 35 * @var int 36 */ 37 protected $minutes; 38 39 /** 40 * The container instance. 41 * 42 * @var \Illuminate\Contracts\Container\Container 43 */ 44 protected $container; 45 46 /** 47 * The existence state of the session. 48 * 49 * @var bool 50 */ 51 protected $exists; 52 53 /** 54 * Create a new database session handler instance. 55 * 56 * @param \Illuminate\Database\ConnectionInterface $connection 57 * @param string $table 58 * @param int $minutes 59 * @param \Illuminate\Contracts\Container\Container|null $container 60 * @return void 61 */ 62 public function __construct(ConnectionInterface $connection, $table, $minutes, Container $container = null) 63 { 64 $this->table = $table; 65 $this->minutes = $minutes; 66 $this->container = $container; 67 $this->connection = $connection; 68 } 69 70 /** 71 * {@inheritdoc} 72 */ 73 public function open($savePath, $sessionName) 74 { 75 return true; 76 } 77 78 /** 79 * {@inheritdoc} 80 */ 81 public function close() 82 { 83 return true; 84 } 85 86 /** 87 * {@inheritdoc} 88 */ 89 public function read($sessionId) 90 { 91 $session = (object) $this->getQuery()->find($sessionId); 92 93 if ($this->expired($session)) { 94 $this->exists = true; 95 96 return ''; 97 } 98 99 if (isset($session->payload)) { 100 $this->exists = true; 101 102 return base64_decode($session->payload); 103 } 104 105 return ''; 106 } 107 108 /** 109 * Determine if the session is expired. 110 * 111 * @param \stdClass $session 112 * @return bool 113 */ 114 protected function expired($session) 115 { 116 return isset($session->last_activity) && 117 $session->last_activity < Carbon::now()->subMinutes($this->minutes)->getTimestamp(); 118 } 119 120 /** 121 * {@inheritdoc} 122 */ 123 public function write($sessionId, $data) 124 { 125 $payload = $this->getDefaultPayload($data); 126 127 if (! $this->exists) { 128 $this->read($sessionId); 129 } 130 131 if ($this->exists) { 132 $this->performUpdate($sessionId, $payload); 133 } else { 134 $this->performInsert($sessionId, $payload); 135 } 136 137 return $this->exists = true; 138 } 139 140 /** 141 * Perform an insert operation on the session ID. 142 * 143 * @param string $sessionId 144 * @param string $payload 145 * @return bool|null 146 */ 147 protected function performInsert($sessionId, $payload) 148 { 149 try { 150 return $this->getQuery()->insert(Arr::set($payload, 'id', $sessionId)); 151 } catch (QueryException $e) { 152 $this->performUpdate($sessionId, $payload); 153 } 154 } 155 156 /** 157 * Perform an update operation on the session ID. 158 * 159 * @param string $sessionId 160 * @param string $payload 161 * @return int 162 */ 163 protected function performUpdate($sessionId, $payload) 164 { 165 return $this->getQuery()->where('id', $sessionId)->update($payload); 166 } 167 168 /** 169 * Get the default payload for the session. 170 * 171 * @param string $data 172 * @return array 173 */ 174 protected function getDefaultPayload($data) 175 { 176 $payload = [ 177 'payload' => base64_encode($data), 178 'last_activity' => $this->currentTime(), 179 ]; 180 181 if (! $this->container) { 182 return $payload; 183 } 184 185 return tap($payload, function (&$payload) { 186 $this->addUserInformation($payload) 187 ->addRequestInformation($payload); 188 }); 189 } 190 191 /** 192 * Add the user information to the session payload. 193 * 194 * @param array $payload 195 * @return $this 196 */ 197 protected function addUserInformation(&$payload) 198 { 199 if ($this->container->bound(Guard::class)) { 200 $payload['user_id'] = $this->userId(); 201 } 202 203 return $this; 204 } 205 206 /** 207 * Get the currently authenticated user's ID. 208 * 209 * @return mixed 210 */ 211 protected function userId() 212 { 213 return $this->container->make(Guard::class)->id(); 214 } 215 216 /** 217 * Add the request information to the session payload. 218 * 219 * @param array $payload 220 * @return $this 221 */ 222 protected function addRequestInformation(&$payload) 223 { 224 if ($this->container->bound('request')) { 225 $payload = array_merge($payload, [ 226 'ip_address' => $this->ipAddress(), 227 'user_agent' => $this->userAgent(), 228 ]); 229 } 230 231 return $this; 232 } 233 234 /** 235 * Get the IP address for the current request. 236 * 237 * @return string 238 */ 239 protected function ipAddress() 240 { 241 return $this->container->make('request')->ip(); 242 } 243 244 /** 245 * Get the user agent for the current request. 246 * 247 * @return string 248 */ 249 protected function userAgent() 250 { 251 return substr((string) $this->container->make('request')->header('User-Agent'), 0, 500); 252 } 253 254 /** 255 * {@inheritdoc} 256 */ 257 public function destroy($sessionId) 258 { 259 $this->getQuery()->where('id', $sessionId)->delete(); 260 261 return true; 262 } 263 264 /** 265 * {@inheritdoc} 266 */ 267 public function gc($lifetime) 268 { 269 $this->getQuery()->where('last_activity', '<=', $this->currentTime() - $lifetime)->delete(); 270 } 271 272 /** 273 * Get a fresh query builder instance for the table. 274 * 275 * @return \Illuminate\Database\Query\Builder 276 */ 277 protected function getQuery() 278 { 279 return $this->connection->table($this->table); 280 } 281 282 /** 283 * Set the existence state for the session. 284 * 285 * @param bool $value 286 * @return $this 287 */ 288 public function setExists($value) 289 { 290 $this->exists = $value; 291 292 return $this; 293 } 294} 295