• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

hisorange / browser-detect / 6279728171

22 Sep 2023 10:31PM UTC coverage: 98.142% (-0.6%) from 98.773%
6279728171

Pull #202

github

hisorange
Fix bad ref in test
Pull Request #202: Release 5.0

317 of 323 relevant lines covered (98.14%)

14.5 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

95.92
/src/Parser.php
1
<?php
2

3
namespace hisorange\BrowserDetect;
4

5
use Illuminate\Http\Request;
6
use League\Pipeline\Pipeline;
7
use Illuminate\Cache\CacheManager;
8
use hisorange\BrowserDetect\Contracts\ParserInterface;
9
use hisorange\BrowserDetect\Contracts\ResultInterface;
10
use hisorange\BrowserDetect\Exceptions\BadMethodCallException;
11

12
/**
13
 * Manages the parsing mechanism.
14
 *
15
 * @package hisorange\BrowserDetect
16
 */
17
final class Parser implements ParserInterface
18
{
19
    /**
20
     * @var CacheManager|null
21
     */
22
    protected $cache;
23

24
    /**
25
     * @var Request|null
26
     */
27
    protected $request;
28

29
    /**
30
     * Runtime cache to reduce the parse calls.
31
     *
32
     * @var array
33
     */
34
    protected $runtime;
35

36
    /**
37
     * Parsing configurations.
38
     *
39
     * @var array
40
     */
41
    protected $config;
42

43
    /**
44
     * Singleton used in standalone mode.
45
     *
46
     * @var self|null
47
     */
48
    protected static $instance;
49

50
    /**
51
     * Parser constructor.
52
     *
53
     * @param CacheManager $cache
54
     * @param Request      $request
55
     * @param array        $config
56
     */
57
    public function __construct($cache = null, $request = null, array $config = [])
58
    {
59
        if ($cache !== null) {
15✔
60
            $this->cache   = $cache;
14✔
61
        }
62

63
        if ($request !== null) {
15✔
64
            $this->request = $request;
14✔
65
        }
66

67
        $this->config = array_replace_recursive(
15✔
68
            require(__DIR__ . '/../config/browser-detect.php'),
15✔
69
            $config
15✔
70
        );
15✔
71

72
        $this->runtime = [];
15✔
73
    }
74

75
    /**
76
     * Read the applied final config.
77
     *
78
     * @return array
79
     */
80
    public function config(): array
81
    {
82
        return $this->config;
×
83
    }
84

85
    /**
86
     * Reflect calls to the result object.
87
     *
88
     * @throws \hisorange\BrowserDetect\Exceptions\BadMethodCallException
89
     *
90
     * @param string $method
91
     * @param array  $params
92
     *
93
     * @return mixed
94
     */
95
    public function __call(string $method, array $params)
96
    {
97
        $result = $this->detect();
3✔
98

99
        // Reflect a method.
100
        if (method_exists($result, $method)) {
3✔
101
            /* @phpstan-ignore-next-line */
102
            return call_user_func_array([$result, $method], $params);
2✔
103
        }
104

105
        throw new BadMethodCallException(
1✔
106
            sprintf('%s method does not exists on the %s object.', $method, ResultInterface::class)
1✔
107
        );
1✔
108
    }
109

110
    /**
111
     * Acts as a facade, but proxies all the call to a singleton.
112
     *
113
     * @param string $method
114
     * @param array $params
115
     *
116
     * @return mixed
117
     */
118
    public static function __callStatic(string $method, array $params)
119
    {
120
        if (!static::$instance) {
2✔
121
            static::$instance = new static();
1✔
122
        }
123

124
        /* @phpstan-ignore-next-line */
125
        return call_user_func_array([static::$instance, $method], $params);
2✔
126
    }
127

128
    /**
129
     * @inheritdoc
130
     */
131
    public function detect(): ResultInterface
132
    {
133
        // Cuts the agent string at 2048 byte, anything longer will be a DoS attack.
134
        $userAgentString = substr(
2✔
135
            $this->getUserAgentString(),
2✔
136
            0,
2✔
137
            $this->config['security']['max-header-length']
2✔
138
        );
2✔
139

140
        return $this->parse($userAgentString);
2✔
141
    }
142

143
    /**
144
     * Wrapper around the request accessor, in standalone mode
145
     * the fn will use the $_SERVER global.
146
     *
147
     * @return string
148
     */
149
    protected function getUserAgentString(): string
150
    {
151
        if ($this->request !== null) {
2✔
152
            return $this->request->userAgent() ?? '';
×
153
        } else {
154
            return isset($_SERVER['HTTP_USER_AGENT']) ? ((string) $_SERVER['HTTP_USER_AGENT']) : '';
2✔
155
        }
156
    }
157

158
    /**
159
     * @inheritdoc
160
     */
161
    public function parse(string $agent): ResultInterface
162
    {
163
        $key = $this->makeHashKey($agent);
23✔
164

165
        if (!isset($this->runtime[$key])) {
23✔
166
            // In standalone mode You can run the parser without cache.
167
            if ($this->cache !== null) {
22✔
168
                $result = $this->cache->remember(
21✔
169
                    $key,
21✔
170
                    $this->config['cache']['interval'],
21✔
171
                    function () use ($agent) {
21✔
172
                        return $this->process($agent);
21✔
173
                    }
21✔
174
                );
21✔
175
            } else {
176
                $result = $this->process($agent);
1✔
177
            }
178

179
            $this->runtime[$key] = $result;
22✔
180
        }
181

182
        return $this->runtime[$key];
23✔
183
    }
184

185
    /**
186
     * Create a unique cache key for the user agent.
187
     *
188
     * @param  string $agent
189
     * @return string
190
     */
191
    protected function makeHashKey(string $agent): string
192
    {
193
        return $this->config['cache']['prefix'] . md5($agent);
22✔
194
    }
195

196
    /**
197
     * Pipe the payload through the stages.
198
     *
199
     * @param  string $agent
200
     * @return ResultInterface
201
     */
202
    protected function process(string $agent): ResultInterface
203
    {
204
        return (new Pipeline())
22✔
205
            ->pipe(new Stages\UAParser())
22✔
206
            ->pipe(new Stages\MobileDetect())
22✔
207
            ->pipe(new Stages\CrawlerDetect())
22✔
208
            ->pipe(new Stages\DeviceDetector())
22✔
209
            ->pipe(new Stages\BrowserDetect())
22✔
210
            ->process(new Payload($agent));
22✔
211
    }
212
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc