Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
17.19% covered (danger)
17.19%
11 / 64
12.50% covered (danger)
12.50%
2 / 16
CRAP
0.00% covered (danger)
0.00%
0 / 1
SessionData
17.19% covered (danger)
17.19%
11 / 64
12.50% covered (danger)
12.50%
2 / 16
858.08
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 / 10
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        /** @var array<array-key, mixed> $sessionData */
45        $sessionData = $session;
46        $instance->data = $sessionData;
47        return $instance;
48    }
49
50    public function writeData(): void
51    {
52        session_write_close();
53        $this->isLocked = true;
54    }
55
56    /**
57     * @return void
58     */
59    #[\Override]
60    public function setGroup(array $group, bool $clear = false): void
61    {
62        foreach ($group as $index => $items) {
63            if ($clear) {
64                $this->clearGroup($index);
65            }
66            if (is_array($items) && 0 < count($items)) {
67                foreach ($items as $key => $value) {
68                    $this->set($key, $value, $index);
69                }
70            }
71        }
72    }
73
74    /**
75     * @SuppressWarnings(Superglobals)
76     */
77    #[\Override]
78    public function set(int|string $key, mixed $value, int|string|null $groupIndex = null): void
79    {
80        if (null === $groupIndex) {
81            $this->data[$key] = self::convertValueToScalar($value);
82        } else {
83            $this->data[$groupIndex][$key] = self::convertValueToScalar($value);
84        }
85        if ($this->isLocked) {
86            throw new \BO\Slim\Exception\SessionLocked();
87        }
88        $_SESSION = $this->data;
89    }
90
91    #[\Override]
92    public function get(int|string $key, int|string|null $groupIndex = null, mixed $default = null): mixed
93    {
94        if (! $this->has($key, $groupIndex)) {
95            return self::convertValueToScalar($default);
96        } elseif (null === $groupIndex) {
97            return $this->data[$key];
98        } else {
99            return $this->data[$groupIndex][$key];
100        }
101    }
102
103    #[\Override]
104    public function getEntity(): mixed
105    {
106        if ($this->entityClass === null) {
107            throw new \Exception("Entity-Class not set");
108        }
109        $sessionContent = clone $this->entityClass;
110        $sessionContent->content = $this->data;
111        return $sessionContent;
112    }
113
114    public function setEntityClass(?object $entityClass): static
115    {
116        $this->entityClass = $entityClass;
117        return $this;
118    }
119
120    /**
121     * @return void
122     */
123    #[\Override]
124    public function remove(int|string $key, int|string|null $groupIndex = null): void
125    {
126        if (null === $groupIndex) {
127            unset($this->data[$key]);
128        } else {
129            unset($this->data[$groupIndex][$key]);
130        }
131    }
132
133    /**
134     * @SuppressWarnings(Superglobals)
135     */
136    #[\Override]
137    public function clearGroup(int|string|null $groupIndex = null): void
138    {
139        if (null !== $groupIndex) {
140            $this->data[$groupIndex] = [];
141            $_SESSION = $this->data;
142        }
143    }
144
145    /**
146     * @SuppressWarnings(Superglobals)
147     */
148    #[\Override]
149    public function clear(): void
150    {
151        if (session_status() === PHP_SESSION_ACTIVE) {
152            $sessionName = session_name();
153            if (is_string($sessionName)) {
154                setcookie($sessionName, '', time() - 3600, '/');
155            }
156            $_SESSION = array();
157            session_destroy();
158        }
159    }
160
161    /**
162     * @SuppressWarnings(Superglobals)
163     * @psalm-api
164     */
165    public function restart(): void
166    {
167        if (session_status() === PHP_SESSION_ACTIVE) {
168            session_regenerate_id(true);
169            $_SESSION = array();
170            $this->data = $_SESSION;
171        }
172    }
173
174    /**
175     * @return bool|null
176     */
177    #[\Override]
178    public function has(int|string $key, int|string|null $groupIndex = null): ?bool
179    {
180        if (null === $groupIndex) {
181            return array_key_exists($key, $this->data);
182        } else {
183            if (array_key_exists($groupIndex, $this->data)) {
184                return array_key_exists($key, $this->data[$groupIndex]);
185            }
186        }
187        return false;
188    }
189
190    /**
191     * @return bool
192     */
193    #[\Override]
194    public function isEmpty(): bool
195    {
196        return empty($this->data);
197    }
198
199    // TODO: Review the use of `mixed` return type.
200    // This method delegates to getSession()->jsonSerialize(), which may return various types.
201    // Consider adding stricter return typing if getSession() can be more precisely typed.
202    #[\Override]
203    public function jsonSerialize(): mixed
204    {
205        return json_encode($this->data);
206    }
207
208    private static function convertValueToScalar(mixed $value): mixed
209    {
210        $encoded = json_encode($value);
211        if ($encoded === false) {
212            return null;
213        }
214        return json_decode($encoded, true);
215    }
216}