Complex classes like Session often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use Session, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
38 | class Session { |
||
39 | use |
||
40 | CRUD, |
||
41 | Singleton; |
||
42 | const INIT_STATE_METHOD = 'init'; |
||
43 | const INITIAL_SESSION_EXPIRATION = 300; |
||
44 | /** |
||
45 | * Id of current session |
||
46 | * |
||
47 | * @var false|string |
||
48 | */ |
||
49 | protected $session_id; |
||
50 | /** |
||
51 | * User id of current session |
||
52 | * |
||
53 | * @var int |
||
54 | */ |
||
55 | protected $user_id; |
||
56 | /** |
||
57 | * @var bool |
||
58 | */ |
||
59 | protected $is_admin; |
||
60 | /** |
||
61 | * @var bool |
||
62 | */ |
||
63 | protected $is_user; |
||
64 | /** |
||
65 | * @var bool |
||
66 | */ |
||
67 | protected $is_bot; |
||
68 | /** |
||
69 | * @var bool |
||
70 | */ |
||
71 | protected $is_guest; |
||
72 | /** |
||
73 | * @var Cache_prefix |
||
74 | */ |
||
75 | protected $cache; |
||
76 | /** |
||
77 | * @var Cache_prefix |
||
78 | */ |
||
79 | protected $users_cache; |
||
80 | protected $data_model = [ |
||
81 | 'id' => 'text', |
||
82 | 'user' => 'int:0', |
||
83 | 'created' => 'int:0', |
||
84 | 'expire' => 'int:0', |
||
85 | 'user_agent' => 'text', |
||
86 | 'remote_addr' => 'text', |
||
87 | 'ip' => 'text', |
||
88 | 'data' => 'json' |
||
89 | ]; |
||
90 | protected $table = '[prefix]sessions'; |
||
91 | /** |
||
92 | * Returns database index |
||
93 | * |
||
94 | * @return int |
||
95 | */ |
||
96 | protected function cdb () { |
||
97 | return Config::instance()->module('System')->db('users'); |
||
98 | } |
||
99 | /** |
||
100 | * Use cookie as source of session id, load session |
||
101 | * |
||
102 | * Bots detection is also done here |
||
103 | */ |
||
104 | protected function init () { |
||
105 | if (!$this->cache) { |
||
106 | $this->cache = new Cache_prefix('sessions'); |
||
107 | $this->users_cache = new Cache_prefix('users'); |
||
108 | } |
||
109 | $this->user_id = User::GUEST_ID; |
||
110 | $this->session_id = null; |
||
111 | Event::instance()->fire('System/Session/init/before'); |
||
112 | $Request = Request::instance(); |
||
113 | /** |
||
114 | * If session exists |
||
115 | */ |
||
116 | if ($Request->cookie('session')) { |
||
117 | $this->user_id = $this->load(); |
||
118 | } elseif (!$Request->api_path) { |
||
119 | /** |
||
120 | * Try to detect bot, not necessary for API request |
||
121 | */ |
||
122 | $this->bots_detection(); |
||
123 | } |
||
124 | $this->update_user_is(); |
||
125 | Event::instance()->fire('System/Session/init/after'); |
||
126 | } |
||
127 | /** |
||
128 | * Try to determine whether visitor is a known bot, bots have no sessions |
||
129 | */ |
||
130 | protected function bots_detection () { |
||
131 | $Cache = $this->users_cache; |
||
132 | $Request = Request::instance(); |
||
133 | /** |
||
134 | * For bots: login is user agent, email is IP |
||
135 | */ |
||
136 | $login = $Request->header('user-agent'); |
||
137 | $email = $Request->ip; |
||
138 | $bot_hash = hash('sha224', $login.$email); |
||
139 | /** |
||
140 | * If bot is cached |
||
141 | */ |
||
142 | $bot_id = $Cache->$bot_hash; |
||
143 | /** |
||
144 | * If bot found in cache - exit from here |
||
145 | */ |
||
146 | if ($bot_id) { |
||
147 | $this->user_id = $bot_id; |
||
148 | return; |
||
149 | } |
||
150 | /** |
||
151 | * Try to find bot among known bots |
||
152 | */ |
||
153 | foreach ($this->all_bots() as $bot) { |
||
154 | if ($this->is_this_bot($bot, $login, $email)) { |
||
155 | /** |
||
156 | * If bot found - save it in cache |
||
157 | */ |
||
158 | $this->user_id = $bot['id']; |
||
159 | $Cache->$bot_hash = $bot['id']; |
||
160 | return; |
||
161 | } |
||
162 | } |
||
163 | } |
||
164 | /** |
||
165 | * Get list of all bots |
||
166 | * |
||
167 | * @return array |
||
168 | */ |
||
169 | protected function all_bots () { |
||
170 | return $this->users_cache->get( |
||
171 | 'bots', |
||
172 | function () { |
||
173 | return $this->db()->qfa( |
||
174 | [ |
||
175 | "SELECT |
||
176 | `u`.`id`, |
||
177 | `u`.`login`, |
||
178 | `u`.`email` |
||
179 | FROM `[prefix]users` AS `u` |
||
180 | INNER JOIN `[prefix]users_groups` AS `g` |
||
181 | ON `u`.`id` = `g`.`id` |
||
182 | WHERE |
||
183 | `g`.`group` = '%s' AND |
||
184 | `u`.`status` = '%s'", |
||
185 | User::BOT_GROUP_ID, |
||
186 | User::STATUS_ACTIVE |
||
187 | ] |
||
188 | ) ?: []; |
||
189 | } |
||
190 | ) ?: []; |
||
191 | } |
||
192 | /** |
||
193 | * Check whether user agent and IP (login and email for bots) corresponds to passed bot data |
||
194 | * |
||
195 | * @param array $bot |
||
196 | * @param string $login |
||
197 | * @param string $email |
||
198 | * |
||
199 | * @return bool |
||
200 | */ |
||
201 | protected function is_this_bot ($bot, $login, $email) { |
||
202 | return |
||
203 | ( |
||
204 | $bot['login'] && |
||
205 | ( |
||
206 | strpos($login, $bot['login']) !== false || |
||
207 | _preg_match($bot['login'], $login) |
||
208 | ) |
||
209 | ) || |
||
210 | ( |
||
211 | $bot['email'] && |
||
212 | ( |
||
213 | $email === $bot['email'] || |
||
214 | _preg_match($bot['email'], $email) |
||
215 | ) |
||
216 | ); |
||
217 | } |
||
218 | /** |
||
219 | * Updates information about who is user accessed by methods ::guest() ::bot() ::user() admin() |
||
220 | */ |
||
221 | protected function update_user_is () { |
||
222 | $this->is_guest = false; |
||
223 | $this->is_bot = false; |
||
224 | $this->is_user = false; |
||
225 | $this->is_admin = false; |
||
226 | if ($this->user_id == User::GUEST_ID) { |
||
227 | $this->is_guest = true; |
||
228 | return; |
||
229 | } |
||
230 | /** |
||
231 | * Checking of user type |
||
232 | */ |
||
233 | $groups = User::instance()->get_groups($this->user_id) ?: []; |
||
234 | if (in_array(User::ADMIN_GROUP_ID, $groups)) { |
||
235 | $this->is_admin = true; |
||
236 | $this->is_user = true; |
||
237 | } elseif (in_array(User::USER_GROUP_ID, $groups)) { |
||
238 | $this->is_user = true; |
||
239 | } elseif (in_array(User::BOT_GROUP_ID, $groups)) { |
||
240 | $this->is_guest = true; |
||
241 | $this->is_bot = true; |
||
242 | } |
||
243 | } |
||
244 | /** |
||
245 | * Is admin |
||
246 | * |
||
247 | * @return bool |
||
248 | */ |
||
249 | function admin () { |
||
250 | return $this->is_admin; |
||
251 | } |
||
252 | /** |
||
253 | * Is user |
||
254 | * |
||
255 | * @return bool |
||
256 | */ |
||
257 | function user () { |
||
258 | return $this->is_user; |
||
259 | } |
||
260 | /** |
||
261 | * Is guest |
||
262 | * |
||
263 | * @return bool |
||
264 | */ |
||
265 | function guest () { |
||
266 | return $this->is_guest; |
||
267 | } |
||
268 | /** |
||
269 | * Is bot |
||
270 | * |
||
271 | * @return bool |
||
272 | */ |
||
273 | function bot () { |
||
274 | return $this->is_bot; |
||
275 | } |
||
276 | /** |
||
277 | * Returns id of current session |
||
278 | * |
||
279 | * @return false|string |
||
280 | */ |
||
281 | function get_id () { |
||
282 | if ($this->user_id == User::GUEST_ID && $this->bot()) { |
||
283 | return false; |
||
284 | } |
||
285 | return $this->session_id ?: false; |
||
286 | } |
||
287 | /** |
||
288 | * Returns user id of current session |
||
289 | * |
||
290 | * @return int |
||
291 | */ |
||
292 | function get_user () { |
||
293 | return $this->user_id; |
||
294 | } |
||
295 | /** |
||
296 | * Returns session details by session id |
||
297 | * |
||
298 | * @param false|null|string $session_id If `null` - loaded from `$this->session_id`, and if that also empty - from cookies |
||
299 | * |
||
300 | * @return false|array |
||
301 | */ |
||
302 | function get ($session_id) { |
||
303 | $session_data = $this->get_internal($session_id); |
||
304 | if ($session_data) { |
||
305 | unset($session_data['data']); |
||
306 | } |
||
307 | return $session_data; |
||
308 | } |
||
309 | /** |
||
310 | * @param false|null|string $session_id |
||
311 | * |
||
312 | * @return false|array |
||
313 | */ |
||
314 | protected function get_internal ($session_id) { |
||
315 | if (!$session_id) { |
||
316 | if (!$this->session_id) { |
||
317 | $this->session_id = Request::instance()->cookie('session'); |
||
318 | } |
||
319 | $session_id = $this->session_id; |
||
320 | } |
||
321 | if (!is_md5($session_id)) { |
||
322 | return false; |
||
323 | } |
||
324 | $data = $this->cache->get( |
||
325 | $session_id, |
||
326 | function () use ($session_id) { |
||
327 | $data = $this->read($session_id); |
||
328 | if (!$data || $data['expire'] <= time()) { |
||
329 | return false; |
||
330 | } |
||
331 | $data['data'] = $data['data'] ?: []; |
||
332 | return $data; |
||
333 | } |
||
334 | ); |
||
335 | return $this->is_good_session($data) ? $data : false; |
||
336 | } |
||
337 | /** |
||
338 | * Check whether session was not expired, user agent and IP corresponds to what is expected and user is actually active |
||
339 | * |
||
340 | * @param mixed $session_data |
||
341 | * |
||
342 | * @return bool |
||
343 | */ |
||
344 | protected function is_good_session ($session_data) { |
||
345 | return |
||
346 | isset($session_data['expire'], $session_data['user']) && |
||
347 | $session_data['expire'] > time() && |
||
348 | $this->is_user_active($session_data['user']); |
||
349 | } |
||
350 | /** |
||
351 | * Whether session data belongs to current visitor (user agent, remote addr and ip check) |
||
352 | * |
||
353 | * @param string $session_id |
||
354 | * @param string $user_agent |
||
355 | * @param string $remote_addr |
||
356 | * @param string $ip |
||
357 | * |
||
358 | * @return bool |
||
359 | */ |
||
360 | function is_session_owner ($session_id, $user_agent, $remote_addr, $ip) { |
||
361 | $session_data = $this->get($session_id); |
||
362 | return $session_data ? $this->is_session_owner_internal($session_data, $user_agent, $remote_addr, $ip) : false; |
||
363 | } |
||
364 | /** |
||
365 | * Whether session data belongs to current visitor (user agent, remote addr and ip check) |
||
366 | * |
||
367 | * @param array $session_data |
||
368 | * @param string|null $user_agent |
||
369 | * @param string|null $remote_addr |
||
370 | * @param string|null $ip |
||
371 | * |
||
372 | * @return bool |
||
373 | */ |
||
374 | protected function is_session_owner_internal ($session_data, $user_agent = null, $remote_addr = null, $ip = null) { |
||
375 | /** |
||
376 | * md5() as protection against timing attacks |
||
377 | */ |
||
378 | if ($user_agent === null && $remote_addr === null && $ip === null) { |
||
379 | $Request = Request::instance(); |
||
380 | $user_agent = $Request->header('user-agent'); |
||
381 | $remote_addr = $Request->remote_addr; |
||
382 | $ip = $Request->ip; |
||
383 | } |
||
384 | return |
||
385 | md5($session_data['user_agent']) == md5($user_agent) && |
||
386 | ( |
||
387 | !Config::instance()->core['remember_user_ip'] || |
||
388 | ( |
||
389 | md5($session_data['remote_addr']) == md5(ip2hex($remote_addr)) && |
||
390 | md5($session_data['ip']) == md5(ip2hex($ip)) |
||
391 | ) |
||
392 | ); |
||
393 | } |
||
394 | /** |
||
395 | * Load session by id and return id of session owner (user), update session expiration |
||
396 | * |
||
397 | * @param false|null|string $session_id If not specified - loaded from `$this->session_id`, and if that also empty - from cookies |
||
398 | * |
||
399 | * @return int User id |
||
400 | */ |
||
401 | function load ($session_id = null) { |
||
402 | if ($this->user_id == User::GUEST_ID && $this->bot()) { |
||
403 | return User::GUEST_ID; |
||
404 | } |
||
405 | $session_data = $this->get_internal($session_id); |
||
406 | if (!$session_data || !$this->is_session_owner_internal($session_data)) { |
||
407 | $this->add(User::GUEST_ID); |
||
408 | return User::GUEST_ID; |
||
409 | } |
||
410 | /** |
||
411 | * Updating last online time and ip |
||
412 | */ |
||
413 | $Config = Config::instance(); |
||
414 | $time = time(); |
||
415 | if ($session_data['expire'] - $time < $Config->core['session_expire'] * $Config->core['update_ratio'] / 100) { |
||
416 | $session_data['expire'] = $time + $Config->core['session_expire']; |
||
417 | $this->update($session_data); |
||
418 | $this->cache->set($session_data['id'], $session_data); |
||
419 | } |
||
420 | unset($session_data['data']); |
||
421 | Event::instance()->fire( |
||
422 | 'System/Session/load', |
||
423 | [ |
||
424 | 'session_data' => $session_data |
||
425 | ] |
||
426 | ); |
||
427 | return $this->load_initialization($session_data['id'], $session_data['user']); |
||
428 | } |
||
429 | /** |
||
430 | * Initialize session (set user id, session id and update who user is) |
||
431 | * |
||
432 | * @param string $session_id |
||
433 | * @param int $user_id |
||
434 | * |
||
435 | * @return int User id |
||
436 | */ |
||
437 | protected function load_initialization ($session_id, $user_id) { |
||
443 | /** |
||
444 | * Whether profile is activated, not disabled and not blocked |
||
445 | * |
||
446 | * @param int $user |
||
447 | * |
||
448 | * @return bool |
||
449 | */ |
||
450 | protected function is_user_active ($user) { |
||
451 | /** |
||
452 | * Optimization, more data requested than actually used here, because data will be requested later, and it would be nice to have that data cached |
||
453 | */ |
||
454 | $data = User::instance()->get( |
||
455 | [ |
||
456 | 'login', |
||
457 | 'username', |
||
458 | 'language', |
||
459 | 'timezone', |
||
460 | 'status', |
||
461 | 'block_until', |
||
494 | /** |
||
495 | * Create the session for the user with specified id |
||
496 | * |
||
497 | * @param int $user |
||
498 | * @param bool $delete_current_session |
||
499 | * |
||
500 | * @return false|string Session id on success, `false` otherwise |
||
501 | */ |
||
502 | function add ($user, $delete_current_session = true) { |
||
534 | /** |
||
535 | * @param int $user |
||
536 | * |
||
537 | * @return array Session data |
||
538 | */ |
||
539 | protected function create_unique_session ($user) { |
||
567 | /** |
||
568 | * Destroying of the session |
||
569 | * |
||
570 | * @param null|string $session_id |
||
571 | * |
||
572 | * @return bool |
||
573 | */ |
||
574 | function del ($session_id = null) { |
||
577 | /** |
||
578 | * Deletion of the session |
||
579 | * |
||
580 | * @param string|null $session_id |
||
581 | * @param bool $create_guest_session |
||
582 | * |
||
583 | * @return bool |
||
584 | */ |
||
585 | protected function del_internal ($session_id = null, $create_guest_session = true) { |
||
613 | /** |
||
614 | * Delete all old sessions from DB |
||
615 | */ |
||
616 | protected function delete_old_sessions () { |
||
622 | /** |
||
623 | * Deletion of all user sessions |
||
624 | * |
||
625 | * @param false|int $user If not specified - current user assumed |
||
626 | * |
||
627 | * @return bool |
||
628 | */ |
||
629 | function del_all ($user = false) { |
||
652 | /** |
||
653 | * Get data, stored with session |
||
654 | * |
||
655 | * @param string $item |
||
656 | * @param null|string $session_id |
||
657 | * |
||
658 | * @return false|mixed |
||
659 | * |
||
660 | */ |
||
661 | function get_data ($item, $session_id = null) { |
||
665 | /* |
||
666 | * @param null|string $session_id |
||
667 | * |
||
668 | * @return array|false |
||
669 | */ |
||
670 | protected function get_data_internal ($session_id) { |
||
674 | /** |
||
675 | * Store data with session |
||
676 | * |
||
677 | * @param string $item |
||
678 | * @param mixed $value |
||
679 | * @param null|string $session_id |
||
680 | * |
||
681 | * @return bool |
||
682 | * |
||
683 | */ |
||
684 | function set_data ($item, $value, $session_id = null) { |
||
699 | /** |
||
700 | * Delete data, stored with session |
||
701 | * |
||
702 | * @param string $item |
||
703 | * @param null|string $session_id |
||
704 | * |
||
705 | * @return bool |
||
706 | * |
||
707 | */ |
||
708 | function del_data ($item, $session_id = null) { |
||
715 | } |
||
716 |