Skip to content

Commit 0aa74d9

Browse files
committed
Detector: Full refactoring [BC break!]
1 parent b43a9b5 commit 0aa74d9

File tree

2 files changed

+131
-33
lines changed

2 files changed

+131
-33
lines changed

.phpstorm.meta.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
\Redbitcz\DebugMode\Detector::__construct(),
77
1,
88
\Redbitcz\DebugMode\Detector::MODE_ALL,
9+
\Redbitcz\DebugMode\Detector::MODE_SIMPLE,
910
\Redbitcz\DebugMode\Detector::MODE_ENABLER,
1011
\Redbitcz\DebugMode\Detector::MODE_COOKIE,
1112
\Redbitcz\DebugMode\Detector::MODE_ENV,
@@ -16,6 +17,18 @@
1617
\Redbitcz\DebugMode\Detector::detect(),
1718
1,
1819
\Redbitcz\DebugMode\Detector::MODE_ALL,
20+
\Redbitcz\DebugMode\Detector::MODE_SIMPLE,
21+
\Redbitcz\DebugMode\Detector::MODE_ENABLER,
22+
\Redbitcz\DebugMode\Detector::MODE_COOKIE,
23+
\Redbitcz\DebugMode\Detector::MODE_ENV,
24+
\Redbitcz\DebugMode\Detector::MODE_IP
25+
);
26+
27+
expectedArguments(
28+
\Redbitcz\DebugMode\Detector::detectProductionMode(),
29+
1,
30+
\Redbitcz\DebugMode\Detector::MODE_ALL,
31+
\Redbitcz\DebugMode\Detector::MODE_SIMPLE,
1932
\Redbitcz\DebugMode\Detector::MODE_ENABLER,
2033
\Redbitcz\DebugMode\Detector::MODE_COOKIE,
2134
\Redbitcz\DebugMode\Detector::MODE_ENV,

src/Detector.php

Lines changed: 118 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -8,33 +8,74 @@
88

99
namespace Redbitcz\DebugMode;
1010

11+
use InvalidArgumentException;
12+
use LogicException;
13+
1114
class Detector
1215
{
13-
private const DEBUG_ENV_NAME = 'APP_DEBUG';
14-
private const DEBUG_COOKIE_NAME = 'app-debug-mode';
16+
/** Name of Environment variable used to detect Debug mode */
17+
public const DEBUG_ENV_NAME = 'APP_DEBUG';
18+
19+
/** Name of Cookie used to detect Debug mode */
20+
public const DEBUG_COOKIE_NAME = 'app-debug-mode';
21+
22+
23+
/** Enables Debug mode detection by client IP */
24+
public const MODE_IP = 0b0001;
25+
26+
/** Enables Debug mode detection by PHP process Environment variable */
27+
public const MODE_ENV = 0b0010;
28+
29+
/** Enables Debug mode detection by request Cookie */
30+
public const MODE_COOKIE = 0b0100;
1531

16-
public const MODE_ENABLER = 0b0001;
17-
public const MODE_COOKIE = 0b0010;
18-
public const MODE_ENV = 0b0100;
19-
public const MODE_IP = 0b1000;
20-
public const MODE_ALL = self::MODE_ENABLER | self::MODE_COOKIE | self::MODE_ENV | self::MODE_IP;
32+
/** Enables Debug mode detection by Enabler */
33+
public const MODE_ENABLER = 0b1000;
2134

22-
/** @var Enabler */
35+
/** Simple mode without Enabler */
36+
public const MODE_SIMPLE = self::MODE_COOKIE | self::MODE_ENV | self::MODE_IP;
37+
38+
/** Full mode with Enabler */
39+
public const MODE_ALL = self::MODE_ENABLER | self::MODE_SIMPLE;
40+
41+
42+
/** @var Enabler|null */
2343
private $enabler;
2444
/** @var int */
2545
private $mode;
46+
/** @var string[] */
47+
private $ips = ['::1', '127.0.0.1'];
2648

27-
public function __construct(string $tempDir, int $mode = self::MODE_ALL)
49+
/**
50+
* @param int $mode Enables methods which is used to detect Debug mode
51+
* @param Enabler|null $enabler Enabler instance. Optional, but required when Enabler mode is enabled
52+
*/
53+
public function __construct(int $mode = self::MODE_SIMPLE, ?Enabler $enabler = null)
2854
{
29-
$this->enabler = new Enabler($tempDir);
55+
if ($enabler === null && $mode & self::MODE_ENABLER) {
56+
throw new InvalidArgumentException('Enabler mode requires Enabler instance in constructor');
57+
}
58+
59+
$this->enabler = $enabler;
3060
$this->mode = $mode;
3161
}
3262

3363
public function getEnabler(): Enabler
3464
{
65+
if ($this->enabler === null) {
66+
throw new LogicException('Detector constructed without Enabler');
67+
}
68+
3569
return $this->enabler;
3670
}
3771

72+
/**
73+
* Detect Debug mode by all method enabled by Detector mode
74+
* Returned value:
75+
* - `false` (force to turn-off debug mode)
76+
* - `true` (force to turn-on debug mode)
77+
* - `null` (unknown/automatic debug mode state)
78+
*/
3879
public function isDebugMode(?bool $default = false): ?bool
3980
{
4081
return ($this->mode & self::MODE_ENABLER ? $this->isDebugModeByEnabler() : null)
@@ -45,17 +86,15 @@ public function isDebugMode(?bool $default = false): ?bool
4586
}
4687

4788
/**
48-
* Detect debug state by DobugModeEnabler helper
89+
* Detect Debug mode by `DebugMode\Enabler` helper
4990
* Returned value:
5091
* - `false` (force to turn-off debug mode)
5192
* - `true` (force to turn-on debug mode)
5293
* - `null` (enabler is not activated)
53-
*
54-
* @return bool|null
5594
*/
5695
public function isDebugModeByEnabler(): ?bool
5796
{
58-
return $this->enabler->isDebug();
97+
return $this->getEnabler()->isDebug();
5998
}
6099

61100
/**
@@ -67,8 +106,6 @@ public function isDebugModeByEnabler(): ?bool
67106
*
68107
* Note: This cookie allows only turn-off Debug mode.
69108
* Using cookie to turn-on debug mode is unsecure!
70-
*
71-
* @return bool|null
72109
*/
73110
public function isDebugModeByCookie(): ?bool
74111
{
@@ -81,31 +118,27 @@ public function isDebugModeByCookie(): ?bool
81118
}
82119

83120
/**
84-
* Detect debug state by ENV parameter
121+
* Detect Debug mode by ENV parameter
85122
* ENV value vs. returned value:
86123
* - `0`: `false` (force to turn-off debug mode)
87124
* - `1`: `true` (force to turn-on debug mode)
88125
* - `undefined` or any other value: `null`
89-
*
90-
* @return bool|null
91126
*/
92127
public function isDebugModeByEnv(): ?bool
93128
{
94129
$envValue = getenv(self::DEBUG_ENV_NAME);
95-
if (is_numeric($envValue) && in_array((int)$envValue, [0, 1], true)) {
130+
if ($envValue !== false && is_numeric($envValue) && in_array((int)$envValue, [0, 1], true)) {
96131
return (int)$envValue === 1;
97132
}
98133

99134
return null;
100135
}
101136

102137
/**
103-
* Detect debug state by locahost IP
138+
* Detect debug state by match allowed IP addresses
104139
* Returned value:
105-
* - is localhost: `true` (force to turn-on debug mode)
106-
* - otherwise: `null`
107-
*
108-
* @return bool|null
140+
* - is matched: `true` (force to turn-on debug mode)
141+
* - not matched: `null`
109142
*/
110143
public function isDebugModeByIp(): ?bool
111144
{
@@ -114,21 +147,73 @@ public function isDebugModeByIp(): ?bool
114147
// Security check: Prevent false-positive match behind reverse proxy
115148
$result = isset($_SERVER['HTTP_X_FORWARDED_FOR']) === false
116149
&& isset($_SERVER['HTTP_FORWARDED']) === false
117-
&& (
118-
$addr === '::1'
119-
|| preg_match('/^127\.0\.0\.\d+$/D', $addr)
120-
);
150+
&& isset($_SERVER['HTTP_X_REAL_IP']) === false
151+
&& in_array($addr, $this->ips, true);
121152

122153
return $result ?: null;
123154
}
124155

125-
public static function detect(string $tempDir, int $mode = self::MODE_ALL, ?bool $default = false): ?bool
156+
/**
157+
* Set client IP address with allowed Debug mode
158+
*/
159+
public function allowIp(string ...$ips): self
126160
{
127-
return (new self($tempDir, $mode))->isDebugMode($default);
161+
$this->ips = $ips;
162+
return $this;
128163
}
129164

130-
public static function detectProductionMode(string $tempDir, ?bool $default = false): ?bool
165+
/**
166+
* Add client IP address with allowed Debug mode
167+
*/
168+
public function addAllowedIp(string ...$ips): self
131169
{
132-
return self::detect($tempDir, $default) === false;
170+
/** @noinspection AdditionOperationOnArraysInspection */
171+
$this->ips += $ips;
172+
return $this;
173+
}
174+
175+
/**
176+
* Shortcut to simple detect Debug mode by all method enabled by Detector mode (argument `$mode`)
177+
*
178+
* @param int $mode Enables methods which is used to detect Debug mode
179+
* @param string|null $tempDir Path to temp directory. Optional, but required when Enabler mode is enabled
180+
* @param bool|null $default Default value when no method matches
181+
*/
182+
public static function detect(
183+
int $mode = self::MODE_SIMPLE,
184+
?string $tempDir = null,
185+
?bool $default = false
186+
): ?bool {
187+
if ($tempDir === null && $mode & self::MODE_ENABLER) {
188+
throw new InvalidArgumentException('Enabler mode requires `tempDir` argument');
189+
}
190+
191+
$enabler = $tempDir === null ? null : new Enabler($tempDir);
192+
return (new self($mode, $enabler))->isDebugMode($default);
193+
}
194+
195+
/**
196+
* Shortcut to simple detect Production mode by all method enabled by Detector mode (argument `$mode`)
197+
*
198+
* @param int $mode Enables methods which is used to detect Debug mode
199+
* @param string|null $tempDir Path to temp directory. Optional, but required when Enabler mode is enabled
200+
* @param bool|null $default Default value when no method matches
201+
*/
202+
public static function detectProductionMode(
203+
int $mode = self::MODE_SIMPLE,
204+
?string $tempDir = null,
205+
?bool $default = false
206+
): ?bool {
207+
if (is_bool($default)) {
208+
$default = !$default;
209+
}
210+
211+
$result = self::detect($mode, $tempDir, $default);
212+
213+
if (is_bool($result)) {
214+
$result = !$result;
215+
}
216+
217+
return $result;
133218
}
134219
}

0 commit comments

Comments
 (0)