Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
17.46% covered (danger)
17.46%
11 / 63
12.50% covered (danger)
12.50%
2 / 16
CRAP
0.00% covered (danger)
0.00%
0 / 1
SessionData
17.46% covered (danger)
17.46%
11 / 63
12.50% covered (danger)
12.50%
2 / 16
850.00
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getSession
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
30
 writeData
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 setGroup
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
42
 set
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
12
 get
80.00% covered (warning)
80.00%
4 / 5
0.00% covered (danger)
0.00%
0 / 1
3.07
 getEntity
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
6
 setEntityClass
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 remove
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 clearGroup
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 clear
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
12
 restart
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 has
80.00% covered (warning)
80.00%
4 / 5
0.00% covered (danger)
0.00%
0 / 1
3.07
 isEmpty
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 jsonSerialize
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 convertValueToScalar
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
1<?php
2
3namespace BO\Slim\Middleware\Session;
4
5use Psr\Http\Message\ServerRequestInterface as Request;
6
7class SessionData implements SessionInterface
8{
9    /** @var array<array-key, mixed> */
10    protected array $data = [];
11
12    private bool $isLocked = false;
13
14    protected ?object $entityClass = null;
15
16    /**
17     * __construct is not allowed, use
18     * - {@see SessionData::getSessionFromName}
19     * instead
20     */
21    public function __construct(?array $data = null)
22    {
23        $this->data = $data ?? [];
24    }
25
26    /**
27     *
28     * @SuppressWarnings(Superglobals)
29     * @SuppressWarnings(Unused)
30     */
31    public static function getSession(Request $request): self
32    {
33        if (headers_sent() === false && session_status() !== PHP_SESSION_ACTIVE) {
34            session_start();
35            if (!isset($_SESSION) || count($_SESSION) === 0) {
36                $_SESSION['status'] = 'start';
37            };
38            $session = $_SESSION;
39        } else {
40            throw  new \BO\Slim\Exception\SessionFailed("Headers sent or a session is already active");
41        }
42
43        $instance = new self();
44        $instance->data = $session;
45        return $instance;
46    }
47
48    public function writeData(): void
49    {
50        session_write_close();
51        $this->isLocked = true;
52    }
53
54    /**
55     * @return void
56     */
57    #[\Override]
58    public function setGroup(array $group, bool $clear = false): void
59    {
60        foreach ($group as $index => $items) {
61            if ($clear) {
62                $this->clearGroup($index);
63            }
64            if (is_array($items) && 0 < count($items)) {
65                foreach ($items as $key => $value) {
66                    $this->set($key, $value, $index);
67                }
68            }
69        }
70    }
71
72    /**
73     * @SuppressWarnings(Superglobals)
74     */
75    #[\Override]
76    public function set(int|string $key, mixed $value, int|string|null $groupIndex = null): void
77    {
78        if (null === $groupIndex) {
79            $this->data[$key] = self::convertValueToScalar($value);
80        } else {
81            $this->data[$groupIndex][$key] = self::convertValueToScalar($value);
82        }
83        if ($this->isLocked) {
84            throw new \BO\Slim\Exception\SessionLocked();
85        }
86        $_SESSION = $this->data;
87    }
88
89    #[\Override]
90    public function get(int|string $key, int|string|null $groupIndex = null, mixed $default = null): mixed
91    {
92        if (! $this->has($key, $groupIndex)) {
93            return self::convertValueToScalar($default);
94        } elseif (null === $groupIndex) {
95            return $this->data[$key];
96        } else {
97            return $this->data[$groupIndex][$key];
98        }
99    }
100
101    #[\Override]
102    public function getEntity(): mixed
103    {
104        if ($this->entityClass === null) {
105            throw new \Exception("Entity-Class not set");
106        }
107        $sessionContent = clone $this->entityClass;
108        $sessionContent->content = $this->data;
109        return $sessionContent;
110    }
111
112    public function setEntityClass(?object $entityClass): static
113    {
114        $this->entityClass = $entityClass;
115        return $this;
116    }
117
118    /**
119     * @return void
120     */
121    #[\Override]
122    public function remove(int|string $key, int|string|null $groupIndex = null): void
123    {
124        if (null === $groupIndex) {
125            unset($this->data[$key]);
126        } else {
127            unset($this->data[$groupIndex][$key]);
128        }
129    }
130
131    /**
132     * @SuppressWarnings(Superglobals)
133     */
134    #[\Override]
135    public function clearGroup(int|string|null $groupIndex = null): void
136    {
137        if (null !== $groupIndex) {
138            $this->data[$groupIndex] = [];
139            $_SESSION = $this->data;
140        }
141    }
142
143    /**
144     * @SuppressWarnings(Superglobals)
145     */
146    #[\Override]
147    public function clear(): void
148    {
149        if (session_status() === PHP_SESSION_ACTIVE) {
150            $sessionName = session_name();
151            if (is_string($sessionName)) {
152                setcookie($sessionName, '', time() - 3600, '/');
153            }
154            $_SESSION = array();
155            session_destroy();
156        }
157    }
158
159    /**
160     * @SuppressWarnings(Superglobals)
161     * @psalm-api
162     */
163    public function restart(): void
164    {
165        if (session_status() === PHP_SESSION_ACTIVE) {
166            session_regenerate_id(true);
167            $_SESSION = array();
168            $this->data = $_SESSION;
169        }
170    }
171
172    /**
173     * @return bool|null
174     */
175    #[\Override]
176    public function has(int|string $key, int|string|null $groupIndex = null): ?bool
177    {
178        if (null === $groupIndex) {
179            return array_key_exists($key, $this->data);
180        } else {
181            if (array_key_exists($groupIndex, $this->data)) {
182                return array_key_exists($key, $this->data[$groupIndex]);
183            }
184        }
185        return false;
186    }
187
188    /**
189     * @return bool
190     */
191    #[\Override]
192    public function isEmpty(): bool
193    {
194        return empty($this->data);
195    }
196
197    // TODO: Review the use of `mixed` return type.
198    // This method delegates to getSession()->jsonSerialize(), which may return various types.
199    // Consider adding stricter return typing if getSession() can be more precisely typed.
200    #[\Override]
201    public function jsonSerialize(): mixed
202    {
203        return json_encode($this->data);
204    }
205
206    private static function convertValueToScalar(mixed $value): mixed
207    {
208        $encoded = json_encode($value);
209        if ($encoded === false) {
210            return null;
211        }
212        return json_decode($encoded, true);
213    }
214}