Completed
Pull Request — master (#2)
by Ben
05:45
created

UrlParser   A

Complexity

Total Complexity 30

Size/Duplication

Total Lines 210
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 2

Test Coverage

Coverage 98.28%

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 210
ccs 57
cts 58
cp 0.9828
rs 10
wmc 30
lcom 1
cbo 2

11 Methods

Rating   Name   Duplication   Size   Complexity  
A resolveRoute() 0 4 1
A __construct() 0 5 1
A set() 0 14 3
A get() 0 19 4
A localize() 0 6 1
A parameters() 0 6 1
A secure() 0 6 1
A localizePath() 0 6 1
B delocalizePath() 0 18 5
C reassemble() 0 13 10
A assertUrlExists() 0 6 2
1
<?php
2
3
namespace Thinktomorrow\Locale\Parsers;
4
5
use Illuminate\Routing\UrlGenerator;
6
use Thinktomorrow\Locale\Locale;
7
8
class UrlParser implements Parser
9
{
10
    /**
11
     * @var Locale
12
     */
13
    private $locale;
14
15
    /**
16
     * If locale is explicitly passed, we will set it
17
     * If null is passed it means the default locale must be used.
18
     *
19
     * @var string
20
     */
21
    private $localeslug = false;
22
23
    /**
24
     * @var array
25
     */
26
    private $parsed;
27
28
    /**
29
     * @var bool
30
     */
31
    private $secure = false;
32
33
    /**
34
     * @var array
35
     */
36
    private $parameters = [];
37
38
    /**
39
     * Internal flag to keep track of schemeless url.
40
     *
41
     * @var bool
42
     */
43
    private $schemeless = false;
44
45
    /**
46
     * @var UrlGenerator
47
     */
48
    private $generator;
49
50 150
    public function __construct(Locale $locale, UrlGenerator $generator)
51
    {
52 150
        $this->locale = $locale;
53 150
        $this->generator = $generator;
54 150
    }
55
56
    /**
57
     * Set the original url/uri.
58
     *
59
     * @param $url
60
     *
61
     * @return $this
62
     */
63 141
    public function set($url)
64
    {
65 141
        $this->parsed = parse_url($url);
0 ignored issues
show
Documentation Bug introduced by
It seems like parse_url($url) can also be of type false. However, the property $parsed is declared as type array. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

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

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
66
67 141
        if (false === $this->parsed) {
68 3
            throw new \InvalidArgumentException('Failed to parse url. Invalid url ['.$url.'] passed as parameter.');
69
        }
70
71
        // If a schemeless url is passed, parse_url will ignore this and strip the first tags
72
        // so we keep a reminder to explicitly reassemble the 'anonymous scheme' manually
73 138
        $this->schemeless = (0 === strpos($url, '//') && isset($this->parsed['host']));
74
75 138
        return $this;
76
    }
77
78
    /**
79
     * Get the localized url.
80
     *
81
     * @return string
82
     */
83 141
    public function get()
84
    {
85 141
        $this->assertUrlExists();
86
87
        // Only localize the url if a locale is passed.
88 138
        if (false !== $this->localeslug) {
89 129
            $this->localizePath($this->localeslug);
90 86
        }
91
92 138
        $url = $this->generator->to($this->reassemble($this->parsed), $this->parameters, $this->secure);
93
94
        // Secure url is not enforced by the urlgenerator when a valid url is passed
95
        // So we enforce it after url generation
96 138
        if ($this->secure && 0 === strpos($url, 'http://')) {
97 6
            $url = str_replace('http://', 'https://', $url);
98 4
        }
99
100 138
        return $url;
101
    }
102
103
    /**
104
     * Resolve the route via the Illuminate UrlGenerator.
105
     *
106
     * @param $routekey
107
     * @param array $parameters
108
     *
109
     * @return string
110
     */
111 81
    public function resolveRoute($routekey, $parameters = [])
112
    {
113 81
        return $this->generator->route($routekey, $parameters, true);
114
    }
115
116
    /**
117
     * Place locale segment in front of url path
118
     * e.g. /foo/bar is transformed into /en/foo/bar.
119
     *
120
     * @param null $localeslug
121
     *
122
     * @return string
123
     */
124 135
    public function localize($localeslug = null)
125
    {
126 135
        $this->localeslug = $localeslug;
127
128 135
        return $this;
129
    }
130
131
    /**
132
     * @param array $parameters
133
     *
134
     * @return $this
135
     */
136 33
    public function parameters(array $parameters = [])
137
    {
138 33
        $this->parameters = $parameters;
139
140 33
        return $this;
141
    }
142
143
    /**
144
     * @param bool $secure
145
     *
146
     * @return $this
147
     */
148 39
    public function secure($secure = true)
149
    {
150 39
        $this->secure = (bool) $secure;
151
152 39
        return $this;
153
    }
154
155
    /**
156
     * Inject the locale slug in the uri.
157
     *
158
     * @param null $locale
159
     */
160 129
    private function localizePath($locale = null)
161
    {
162 129
        $this->parsed['path'] = str_replace('//', '/',
163 129
            '/'.$this->locale->getSlug($locale).$this->delocalizePath()
164 86
        );
165 129
    }
166
167
    /**
168
     * @return array
169
     */
170 129
    private function delocalizePath()
171
    {
172 129
        if (!isset($this->parsed['path'])) {
173 21
            return;
174
        }
175
176 129
        $path_segments = explode('/', trim($this->parsed['path'], '/'));
177
178 129
        if (count($path_segments) < 1) {
179
            return;
180
        }
181
182 129
        if ($path_segments[0] == $this->locale->getSlug($path_segments[0]) || $this->locale->isHidden($path_segments[0])) {
183 36
            unset($path_segments[0]);
184 24
        }
185
186 129
        return '/'.implode('/', $path_segments);
187
    }
188
189
    /**
190
     * Construct a full url with the parsed url elements
191
     * resulted from a parse_url() function call.
192
     *
193
     * @param array $parsed
194
     *
195
     * @return string
196
     */
197 138
    private function reassemble(array $parsed)
198
    {
199 138
        $scheme = (isset($parsed['scheme'])) ? $parsed['scheme'].'://' : ($this->schemeless ? '//' : '');
200
201
        return
202
            $scheme
203 138
            .((isset($parsed['user'])) ? $parsed['user'].((isset($parsed['pass'])) ? ':'.$parsed['pass'] : '').'@' : '')
204 138
            .((isset($parsed['host'])) ? $parsed['host'] : '')
205 138
            .((isset($parsed['port'])) ? ':'.$parsed['port'] : '')
206 138
            .((isset($parsed['path'])) ? $parsed['path'] : '')
207 138
            .((isset($parsed['query'])) ? '?'.$parsed['query'] : '')
208 138
            .((isset($parsed['fragment'])) ? '#'.$parsed['fragment'] : '');
209
    }
210
211 141
    private function assertUrlExists()
212
    {
213 141
        if (!$this->parsed) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->parsed of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
214 3
            throw new \LogicException('Url is required. Please run UrlParser::set($url) first.');
215
        }
216 138
    }
217
}
218