Completed
Push — master ( 3c15fb...e76d2b )
by Ivan
02:41
created

DefaultBrowserLocale::getLanguage()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 0
loc 4
ccs 2
cts 2
cp 1
rs 10
cc 1
eloc 2
nc 1
nop 1
crap 1
1
<?php
2
3
namespace CodeZero\BrowserLocale;
4
5
class DefaultBrowserLocale implements BrowserLocale
6
{
7
    /**
8
     * Array of all preferred locales that are
9
     * configured in a visitor's browser.
10
     *
11
     * @var array
12
     */
13
    private $locales = [];
14
15
    /**
16
     * Create a new DefaultBrowserLocale instance.
17
     *
18
     * @param string $httpAcceptLanguages
19
     */
20 8
    public function __construct($httpAcceptLanguages = null)
0 ignored issues
show
Coding Style introduced by
__construct uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
21
    {
22
        // $_SERVER["HTTP_ACCEPT_LANGUAGE"] will return a comma separated list of language codes.
23
        // Each language code MAY have a "relative quality factor" attached ("nl;q=0.8") which
24
        // determines the order of preference. For example: "nl-NL,nl;q=0.8,en-US;q=0.6,en;q=0.4"
25 8
        if ($httpAcceptLanguages === null && isset($_SERVER["HTTP_ACCEPT_LANGUAGE"])) {
26
            $this->parseAcceptLanguages($_SERVER["HTTP_ACCEPT_LANGUAGE"]);
27
        }
28
29 8
        if ($httpAcceptLanguages) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $httpAcceptLanguages of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
30 7
            $this->parseAcceptLanguages($httpAcceptLanguages);
31 7
        }
32 8
    }
33
34
    /**
35
     * Get the most preferred locale.
36
     *
37
     * @return Locale|null
38
     */
39 3
    public function getLocale()
40
    {
41 3
        return isset($this->locales[0]) ? $this->locales[0] : null;
42
    }
43
44
    /**
45
     * Get an array of Locale objects in descending order of preference.
46
     *
47
     * If a property filter is specified, a flattened array of locale information,
48
     * containing only the requested property values will be returned instead.
49
     *
50
     * @param string $propertyFilter locale|language|country|weight
51
     *
52
     * @return array
53
     */
54 6
    public function getLocales($propertyFilter = null)
55
    {
56 6
        if ($propertyFilter === null) {
57 2
            return $this->locales;
58
        }
59
60 5
        return $this->filterLocaleInfo($propertyFilter);
61
    }
62
63
    /**
64
     * Parse all HTTP Accept Languages.
65
     *
66
     * @param string $httpAcceptLanguages
67
     *
68
     * @return void
69
     */
70 7
    private function parseAcceptLanguages($httpAcceptLanguages)
71
    {
72 7
        foreach ($this->splitAcceptLanguages($httpAcceptLanguages) as $httpAcceptLanguage) {
73 7
            if ($locale = $this->parseAcceptLanguage($httpAcceptLanguage)) {
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $locale is correct as $this->parseAcceptLanguage($httpAcceptLanguage) (which targets CodeZero\BrowserLocale\D...::parseAcceptLanguage()) seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
74 7
                $this->locales[] = $locale;
75 7
            }
76 7
        }
77
78 7
        $this->sortLocales();
79 7
    }
80
81
    /**
82
     * Extract and save information from a HTTP Accept Language.
83
     *
84
     * @param string $httpAcceptLanguage
85
     *
86
     * @return Locale|null
87
     */
88 7
    private function parseAcceptLanguage($httpAcceptLanguage)
89
    {
90 7
        $parts = $this->splitAcceptLanguage($httpAcceptLanguage);
91
92 7
        $locale = isset($parts[0]) ? $parts[0] : null;
93 7
        $weight = isset($parts[1]) ? $parts[1] : null;
94
95 7
        $localeInstance = null;
96
97 7
        if ($locale !== null) {
98 7
            $localeInstance = new Locale();
99 7
            $localeInstance->locale = $locale;
100 7
            $localeInstance->language = $this->getLanguage($locale);
101 7
            $localeInstance->country = $this->getCountry($locale);
102 7
            $localeInstance->weight = $this->getWeight($weight);
103 7
        }
104
105 7
        return $localeInstance;
106
    }
107
108
    /**
109
     * Convert a comma separated list to an array.
110
     *
111
     * Example: ["en", "en-US;q=0.8"]
112
     *
113
     * @param string $httpAcceptLanguages
114
     *
115
     * @return array
116
     */
117 7
    private function splitAcceptLanguages($httpAcceptLanguages)
118
    {
119 7
        return explode(',', $httpAcceptLanguages) ?: [];
120
    }
121
122
    /**
123
     * Split a language code and the relative quality factor by semicolon.
124
     *
125
     * Example: ["en"] or ["en-US"] or ["en-US", "q=0.8"]
126
     *
127
     * @param string $httpAcceptLanguage
128
     *
129
     * @return array
130
     */
131 7
    private function splitAcceptLanguage($httpAcceptLanguage)
132
    {
133 7
        return explode(';', trim($httpAcceptLanguage)) ?: [];
134
    }
135
136
    /**
137
     * Get the 2-letter language code from the locale.
138
     *
139
     * Example: "en"
140
     *
141
     * @param string $locale
142
     *
143
     * @return string
144
     */
145 7
    private function getLanguage($locale)
146
    {
147 7
        return strtolower(substr($locale, 0, 2));
148
    }
149
150
    /**
151
     * Get the 2-letter country code from the locale.
152
     *
153
     * Example: "US"
154
     *
155
     * @param string $locale
156
     *
157
     * @return string
158
     */
159 7
    private function getCountry($locale)
160
    {
161 7
        if (($divider = strpos($locale, '-')) === false){
162 7
            return '';
163
        }
164
165 7
        return strtoupper(substr($locale, $divider + 1, 2));
166
    }
167
168
    /**
169
     * Parse the relative quality factor and return its value.
170
     *
171
     * Example: 1.0 or 0.8
172
     *
173
     * @param string $q
174
     *
175
     * @return float
176
     */
177 7
    private function getWeight($q)
178
    {
179 7
        $weight = 1.0;
180 7
        $parts = explode('=', $q);
181
182 7
        if (isset($parts[1])) {
183 7
            $weight = ((float) $parts[1]);
184 7
        }
185
186 7
        return $weight;
187
    }
188
189
    /**
190
     * Sort the array of locales in descending order of preference.
191
     *
192
     * @return void
193
     */
194
    private function sortLocales()
195
    {
196 7
        usort($this->locales, function ($a, $b) {
197 7
            if ($a->weight === $b->weight) {
198 1
                return 0;
199
            }
200
201 7
            return ($a->weight > $b->weight) ? -1 : 1;
202 7
        });
203 7
    }
204
205
    /**
206
     * Get a flattened array of locale information,
207
     * containing only the requested property values.
208
     *
209
     * @param string $property
210
     *
211
     * @return array
212
     */
213 5
    private function filterLocaleInfo($property)
214
    {
215 5
        $filters = ['locale', 'language', 'country', 'weight'];
216 5
        $locales = $this->locales;
217
218 5
        if ( ! in_array($property, $filters)) {
219 1
            return $locales;
220
        }
221
222 5
        $filtered = [];
223
224 5
        foreach ($locales as $locale) {
225 4
            if ($locale->$property && ! in_array($locale->$property, $filtered)) {
226 4
                $filtered[] = $locale->$property;
227 4
            }
228 5
        }
229
230 5
        return $filtered;
231
    }
232
}
233