Passed
Push — master ( 41e10c...0c10bb )
by Alexander
03:14
created

Store::getOldInput()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 1
c 1
b 0
f 0
nc 1
nop 2
dl 0
loc 3
rs 10
1
<?php 
2
3
/**
4
 * Lenevor Framework
5
 *
6
 * LICENSE
7
 *
8
 * This source file is subject to the new BSD license that is bundled
9
 * with this package in the file license.md.
10
 * It is also available through the world-wide-web at this URL:
11
 * https://lenevor.com/license
12
 * If you did not receive a copy of the license and are unable to
13
 * obtain it through the world-wide-web, please send an email
14
 * to [email protected] so we can send you a copy immediately.
15
 *
16
 * @package     Lenevor
17
 * @subpackage  Base
18
 * @link        https://lenevor.com
19
 * @copyright   Copyright (c) 2019 - 2023 Alexander Campo <[email protected]>
20
 * @license     https://opensource.org/licenses/BSD-3-Clause New BSD license or see https://lenevor.com/license or see /license.md
21
 */
22
23
namespace Syscodes\Components\Session;
24
25
use stdClass;
26
use SessionHandlerInterface;
27
use Syscodes\Components\Support\Arr;
28
use Syscodes\Components\Support\Str;
29
use Syscodes\Components\Support\MessageBag;
30
use Syscodes\Components\Support\ViewErrorBag;
31
use Syscodes\Components\Contracts\Session\Session;
32
use Syscodes\Components\Session\Handlers\CookieSessionHandler;
33
34
/**
35
 * Implementation of Lenevor session container.
36
 */
37
class Store implements Session
38
{
39
    /**
40
     * The session ID.
41
     * 
42
     * @var string $id
43
     */
44
    protected $id;
45
46
    /**
47
     * The session items.
48
     * 
49
     * @var array $items
50
     */
51
    protected $items = [];
52
53
    /**
54
     * The handler session.
55
     * 
56
     * @var \SessionHandlerInterface $handler
57
     */
58
    protected $handler;
59
60
    /**
61
     * The session name.
62
     * 
63
     * @var string $name.
64
     */
65
    protected $name;
66
67
    /**
68
     * The session store's serialization.
69
     * 
70
     * @var string  $serialization
71
     */
72
    protected $serialization = 'php';
73
74
    /**
75
     * Session store started status.
76
     * 
77
     * @var bool $started
78
     */
79
    protected $started = false;
80
81
    /**
82
     * Constructor. The Store class instance.
83
     * 
84
     * @param  string  $name
85
     * @param  \SessionHandlerInterface  $handler
86
     * @param  string|null  $id
87
     * @param  string  $serialization
88
     * 
89
     * @return void
90
     */
91
    public function __construct(
92
        $name, 
93
        SessionHandlerInterface $handler, 
94
        $id = null, 
95
        $serialization = 'php'
96
    ) {
97
        $this->setId($id);
98
99
        $this->name          = $name;
100
        $this->handler       = $handler;
101
        $this->serialization = $serialization;
102
    }
103
104
    /**
105
     * Get the name of the session.
106
     * 
107
     * @return string
108
     */
109
    public function getName(): string
110
    {
111
        return $this->name;
112
    }
113
114
    /**
115
     * Set the name of the session.
116
     * 
117
     * @param  string  $name
118
     * 
119
     * @return void
120
     */
121
    public function setName($name): void
122
    {
123
        $this->name = $name;
124
    }
125
126
    /**
127
     * Start the session.
128
     * 
129
     * @return bool
130
     */
131
    public function start(): bool
132
    {
133
        $this->loadSession();
134
135
        if ( ! $this->has('_token')) {
136
            $this->regenerateToken();
137
        }
138
139
        return $this->started = true;
140
    }
141
142
    /**
143
     * Load the session data from the handler.
144
     * 
145
     * @return void
146
     */
147
    protected function loadSession(): void
148
    {
149
        $this->items = array_merge($this->items, $this->readToHandler());
150
151
        $this->getErrorBag();
152
    }
153
154
    /**
155
     * Read the session data from the handler.
156
     * 
157
     * @return array
158
     */
159
    protected function readToHandler(): array
160
    {
161
        if ($data = $this->handler->read($this->getId())) {
162
            if ($this->serialization === 'json') {
163
               $data = json_decode($this->prepareForUnserialize($data), true);
164
            } else {
165
               $data = @unserialize($this->prepareForUnserialize($data));
166
            }
167
            
168
            if ($data !== false && is_array($data)) {
169
                return $data;
170
            }
171
        }
172
        
173
        return [];
174
    }
175
    
176
    /**
177
     * Prepare the raw string data from the session for unserialization.
178
     * 
179
     * @param  string  $data
180
     * 
181
     * @return string
182
     */
183
    protected function prepareForUnserialize($data): string
184
    {
185
        return $data;
186
    }
187
    
188
    /**
189
     * Get the ViewErrorBag when using JSON serialization for sessions.
190
     * 
191
     * @return void
192
     */
193
    protected function getErrorBag(): void
194
    {
195
        if ($this->serialization !== 'json' || ! $this->exists('errors')) {
196
            return;
197
        }
198
        
199
        $errorBag = new ViewErrorBag;
200
        
201
        foreach ($this->get('errors') as $key => $value) {
202
            $messageBag = new MessageBag($value['messages']);
203
            
204
            $errorBag->put($key, $messageBag->setFormat($value['format']));
205
        }
206
        
207
        $this->put('errors', $errorBag);
208
    }
209
210
    /**
211
     * Get all of the session data.
212
     * 
213
     * @return array
214
     */
215
    public function all(): array
216
    {
217
        return $this->items;
218
    }
219
220
    /**
221
     * Get a subset of the session data.
222
     * 
223
     * @param  array  $keys
224
     * 
225
     * @return array
226
     */
227
    public function only(array $keys): array
228
    {
229
        return Arr::only($this->items, $keys);
230
    }
231
232
    /**
233
     * Get the current session ID.
234
     * 
235
     * @return string
236
     */
237
    public function getId(): string
238
    {
239
        return $this->id;
240
    }
241
242
    /**
243
     * Set the session ID.
244
     * 
245
     * @param  string  $id
246
     * 
247
     * @return void
248
     */
249
    public function setId($id): void
250
    {
251
        $this->id = $this->isValidId($id) ? $id : $this->generateSessionId();
252
    }
253
    
254
    /**
255
     * Determine if this is a valid session ID.
256
     * 
257
     * @param  string  $id
258
     * 
259
     * @return bool
260
     */
261
    public function isValidId($id): bool
262
    {
263
        return is_string($id) && ctype_alnum($id) && strlen($id) === 40;
264
    }
265
    
266
    /**
267
     * Get a new, random session ID.
268
     * 
269
     * @return string
270
     */
271
    protected function generateSessionId(): string
272
    {
273
        return Str::random(40);
274
    }
275
276
    /**
277
     * Save the session data to storage.
278
     * 
279
     * @return void
280
     */
281
    public function save(): void
282
    {
283
        $this->ageFlashData();
284
285
        $this->getErrorBagToSerialization();
286
287
        $this->handler->write($this->getId(), $this->prepareForStorage(
288
            $this->serialization === 'json' ? json_encode($this->items) : serialize($this->items)
289
        ));
290
291
        $this->started = false;
292
    }
293
    
294
    /**
295
     * Get the ViewErrorBag instance for JSON serialization.
296
     * 
297
     * @return void
298
     */
299
    protected function getErrorBagToSerialization(): void
300
    {
301
        if ($this->serialization !== 'json' || ! $this->exists('errors')) {
302
            return;
303
        }
304
        
305
        $errors = [];
306
        
307
        foreach ($this->items['errors']->getBags() as $key => $value) {
308
            $errors[$key] = [
309
                'format' => $value->getFormat(),
310
                'messages' => $value->getMessages(),
311
            ];
312
        }
313
        
314
        $this->items['errors'] = $errors;
315
    }
316
317
    /**
318
     * Age the flash data for the session.
319
     * 
320
     * @return void
321
     */
322
    public function ageFlashData(): void
323
    {
324
        foreach($this->get('_flash.old', []) as $old) {
325
            $this->erase($old);
326
        }
327
328
        $this->put('_flash.old', $this->get('_flash.new', []));
329
        $this->put('_flash.new', []);        
330
    }
331
    
332
    /**
333
     * Prepare the serialized session data for storage.
334
     * 
335
     * @param  string  $data
336
     * 
337
     * @return string
338
     */
339
    protected function prepareForStorage($data): string
340
    {
341
        return $data;
342
    }
343
344
    /**
345
     * Remove one or many items from the session.
346
     * 
347
     * @param  string  $key
348
     * @param  mixed  $default
349
     * 
350
     * @return mixed
351
     */
352
    public function pull($key, $default = null): mixed
353
    {
354
        return Arr::pull($this->items, $key, $default);
355
    }
356
357
    /**
358
     * Push a value onto a session array.
359
     * 
360
     * @param  string  $key
361
     * @param  mixed  $value
362
     * 
363
     * @return void
364
     */
365
    public function push($key, $value): void
366
    {
367
        $array = $this->get($key, []);
368
369
        $array[] = $value;
370
371
        $this->put($key, $array);
372
    }
373
    
374
    /**
375
     * Checks if a key exists.
376
     * 
377
     * @param  string|array  $key
378
     * 
379
     * @return bool
380
     */
381
    public function exists($key): bool
382
    {
383
        $placeholder = new stdClass;
384
        
385
        return ! collect(is_array($key) ? $key : func_get_args())->contains(function ($key) use ($placeholder) {
386
            return $this->get($key, $placeholder) === $placeholder;
387
        });
388
    }
389
    
390
    /**
391
     * Determine if the session contains old input.
392
     * 
393
     * @param  string|null  $key
394
     * 
395
     * @return bool
396
     */
397
    public function hasOldInput($key = null): bool
398
    {
399
        $old = $this->getOldInput($key);
400
        
401
        return is_null($key) ? count($old) > 0 : ! is_null($old);
402
    }
403
    
404
    /**
405
     * Get the requested item from the flashed input array.
406
     * 
407
     * @param  string|null  $key
408
     * @param  mixed  $default
409
     * 
410
     * @return mixed
411
     */
412
    public function getOldInput($key = null, $default = null)
413
    {
414
        return Arr::get($this->get('_old_input', []), $key, $default);
415
    }
416
417
    /**
418
     * Checks if an a key is present and not null.
419
     * 
420
     * @param  string|array  $key
421
     * 
422
     * @return bool
423
     */
424
    public function has($key): bool
425
    {
426
        return ! is_null($this->get($key));
0 ignored issues
show
Bug introduced by
It seems like $key can also be of type array; however, parameter $key of Syscodes\Components\Session\Store::get() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

426
        return ! is_null($this->get(/** @scrutinizer ignore-type */ $key));
Loading history...
427
    }
428
429
    /**
430
     * Get an key from the session, if it doesn´t exists can be use
431
     * the default value as the second argument to the get method.
432
     * 
433
     * @param  string  $key
434
     * @param  mixed  $default
435
     * 
436
     * @return mixed
437
     */
438
    public function get($key, $default = null): mixed
439
    {
440
        return Arr::get($this->items, $key, $default);
441
    }
442
443
    /**
444
     * Replace the given session attributes entirely.
445
     * 
446
     * @param  array  $attributes
447
     * 
448
     * @return void
449
     */
450
    public function replace(array $attributes): void
451
    {
452
        $this->put($attributes);
453
    }
454
455
    /**
456
     * Put a key / value pair or array of key / value pairs in the session.
457
     * 
458
     * @param  string|array  $key
459
     * @param  mixed  $value
460
     * 
461
     * @return void
462
     */
463
    public function put($key, $value = null): void
464
    {
465
        if ( ! is_array($key)) {
466
            $key = [$key => $value];
467
        }
468
469
        foreach ($key as $itemKey => $itemValue) {
470
            Arr::set($this->items, $itemKey, $itemValue);
471
        }
472
    }
473
474
    /**
475
     * Remove an key from the session.
476
     * 
477
     * @param  string  $key
478
     * 
479
     * @return mixed
480
     */
481
    public function remove($key): mixed
482
    {
483
        return Arr::pull($this->items, $key);
484
    }
485
486
    /**
487
     * Remove one or many items from the session.
488
     * 
489
     * @param  string|array  $keys
490
     * 
491
     * @return void
492
     */
493
    public function erase($keys): void
494
    {
495
        Arr::erase($this->items, $keys);
496
    }
497
498
    /**
499
     * Flash a key / value pair to the session.
500
     * 
501
     * @param  string  $key
502
     * @param  mixed  $value
503
     * 
504
     * @return void
505
     */
506
    public function flash(string $key, $value = true): void
507
    {
508
        $this->put($key, $value);
509
        $this->push('_flash.new', $value);
510
        $this->removeOldFlashData([$key]);
511
    }
512
513
    /**
514
     * Remove the given keys from the old flash data.
515
     * 
516
     * @param  array  $keys
517
     * 
518
     * @return void
519
     */
520
    protected function removeOldFlashData(array $keys): void
521
    {
522
        $this->put('_flash.old', array_diff($this->get('_flash.old', []), $keys));
523
    }
524
    
525
    /**
526
     * Flash an input array to the session.
527
     * 
528
     * @param  array  $value
529
     * 
530
     * @return void
531
     */
532
    public function flashInput(array $value): void
533
    {
534
        $this->flash('_old_input', $value);
535
    }
536
537
    /**
538
     * Remove all of the keys from the session.
539
     * 
540
     * @return void
541
     */
542
    public function flush(): void
543
    {
544
        $this->items = [];
545
    }
546
    
547
    /**
548
     * Flush the session data and regenerate the ID.
549
     * 
550
     * @return bool
551
     */
552
    public function invalidate(): bool
553
    {
554
        $this->flush();
555
        
556
        return $this->migrate(true);
557
    }
558
559
    /**
560
     * Get the CSRF token value.
561
     * 
562
     * @return string
563
     */
564
    public function token(): string
565
    {
566
        return $this->get('_token');
567
    }
568
569
    /**
570
     * Regenerate the CSRF token value.
571
     * 
572
     * @return void
573
     */
574
    public function regenerateToken(): void
575
    {
576
        $this->put('_token', Str::random(40));
577
    }
578
579
    /**
580
     * Generate a new session identifier.
581
     * 
582
     * @param  bool  $destroy
583
     * 
584
     * @return callable
585
     */
586
    public function regenerate($destroy = false): callable
587
    {
588
        return take($this->migrate($destroy), function () {
0 ignored issues
show
Bug Best Practice introduced by
The expression return take($this->migra...ion(...) { /* ... */ }) could return the type Syscodes\Components\Support\HigherOrderTakeProxy which is incompatible with the type-hinted return callable. Consider adding an additional type-check to rule them out.
Loading history...
589
            $this->regenerateToken();
590
        });
591
    }
592
593
    /**
594
     * Generate a new session ID for the session.
595
     * 
596
     * @param  bool  $destroy
597
     * 
598
     * @return bool
599
     */
600
    public function migrate(bool $destroy = false): bool
601
    {
602
        if ($destroy) {
603
            $this->handler->destroy($this->getId());
604
        }
605
606
        $this->setId($this->generateSessionId());
607
608
        return $this->started = true;
609
    }
610
611
    /**
612
     * Get the previous URL from the session.
613
     * 
614
     * @return string|null
615
     */
616
    public function previousUrl(): string|null
617
    {
618
        return $this->get('_previous.url');
619
    }
620
    
621
    /**
622
     * Set the "previous" URL in the session.
623
     * 
624
     * @param  string  $url
625
     * 
626
     * @return void
627
     */
628
    public function setPreviousUrl($url): void
629
    {
630
        $this->put('_previous.url', $url);
631
    }
632
633
    /**
634
     * Get the session handler instance.
635
     * 
636
     * @return \SessionHandlerInterface
637
     */
638
    public function getHandler()
639
    {
640
        return $this->handler;
641
    }
642
643
    /**
644
     * Determine if the session has been started.
645
     * 
646
     * @return bool
647
     */
648
    public function isStarted(): bool
649
    {
650
        return $this->started;
651
    }
652
653
    /**
654
     * Determine if the session handler needs a request.
655
     *
656
     * @return bool
657
     */
658
    public function handlerNeedsRequest(): bool
659
    {
660
        return $this->handler instanceof CookieSessionHandler;
661
    }
662
663
    /**
664
     * Set the request on the handler instance.
665
     *
666
     * @param  \Syscodes\Components\Http\Request  $request
667
     * 
668
     * @return void
669
     */
670
    public function setRequestOnHandler($request): void
671
    {
672
        if ($this->handlerNeedsRequest()) {
673
            $this->handler->setRequest($request);
674
        }
675
    }
676
}