Completed
Push — master ( 214db2...5e5895 )
by Elf
04:31 queued 02:59
created

AppManager::routes()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 15
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 3

Importance

Changes 0
Metric Value
cc 3
eloc 8
nc 3
nop 1
dl 0
loc 15
ccs 10
cts 10
cp 1
crap 3
rs 9.4285
c 0
b 0
f 0
1
<?php
2
3
namespace ElfSundae\Laravel\Apps;
4
5
use Illuminate\Support\Arr;
6
use Illuminate\Support\Str;
7
use Illuminate\Support\Traits\Macroable;
8
use Illuminate\Contracts\Container\Container;
9
10
class AppManager
11
{
12
    use Macroable;
13
14
    /**
15
     * The container instance.
16
     *
17
     * @var \Illuminate\Contracts\Container\Container
18
     */
19
    protected $container;
20
21
    /**
22
     * The current application identifier.
23
     *
24
     * @var string|false
25
     */
26
    protected $appId = false;
27
28
    /**
29
     * Create a new app manager instance.
30
     *
31
     * @param  \Illuminate\Contracts\Container\Container  $container
32
     */
33 14
    public function __construct(Container $container)
34
    {
35 14
        $this->container = $container;
36
37 14
        $this->container->rebinding('request', function () {
38 2
            $this->refreshId();
39 14
        });
40 14
    }
41
42
    /**
43
     * Get all application URLs.
44
     *
45
     * @return array
46
     */
47 11
    public function appUrls()
48
    {
49 11
        return $this->container['config']->get('apps.url', []);
50
    }
51
52
    /**
53
     * Get the root URL for the given application.
54
     *
55
     * @param  string  $app
56
     * @return string
57
     */
58 6
    public function appUrl($app = '')
59
    {
60 6
        return Arr::get($this->appUrls(), (string) $app)
61 6
            ?: $this->container['config']['app.url'];
62
    }
63
64
    /**
65
     * Get the root URL for the given application.
66
     *
67
     * @param  string  $app
68
     * @return string
69
     */
70 5
    public function root($app = '')
71
    {
72 5
        return $this->appUrl($app);
73
    }
74
75
    /**
76
     * Get the URL domain for the given application.
77
     *
78
     * @param  string  $app
79
     * @return string
80
     */
81 2
    public function domain($app = '')
82
    {
83 2
        return parse_url($this->root($app), PHP_URL_HOST);
84
    }
85
86
    /**
87
     * Get the URL prefix for the given application.
88
     *
89
     * @param  string  $app
90
     * @return string
91
     */
92 2
    public function prefix($app = '')
93
    {
94 2
        return trim(parse_url($this->root($app), PHP_URL_PATH), '/');
95
    }
96
97
    /**
98
     * Get or check the current application identifier.
99
     *
100
     * @return string|bool
101
     */
102 3
    public function id()
103
    {
104 3
        if ($this->appId === false) {
105 3
            $this->appId = $this->appIdForUrl($this->container['request']->getUri());
106
        }
107
108 3
        if (func_num_args() > 0) {
109 1
            return in_array($this->appId, is_array(func_get_arg(0)) ? func_get_arg(0) : func_get_args());
110
        }
111
112 2
        return $this->appId;
113
    }
114
115
    /**
116
     * Refresh the current application identifier.
117
     *
118
     * @return $this
119
     */
120 3
    public function refreshId()
121
    {
122 3
        $this->appId = false;
123
124 3
        return $this;
125
    }
126
127
    /**
128
     * Get the application identifier for the given URL.
129
     *
130
     * @param  string  $url
131
     * @return string
132
     */
133 4
    public function appIdForUrl($url)
134
    {
135 4
        return collect($this->appUrls())
136 4
            ->filter(function ($root) use ($url) {
137 4
                return $this->urlHasRoot($url, $root);
138 4
            })
139 4
            ->sortByDesc(function ($root) {
140 4
                return strlen($root);
141 4
            })
142 4
            ->keys()
143 4
            ->first();
144
    }
145
146
    /**
147
     * Determine if an URL has the given root URL.
148
     *
149
     * @param  string  $url
150
     * @param  string  $root
151
     * @param  bool  $strict
152
     * @return bool
153
     */
154 4
    protected function urlHasRoot($url, $root, $strict = false)
155
    {
156 4
        if (! $strict) {
157 4
            $url = $this->removeScheme($url);
158 4
            $root = $this->removeScheme($root);
159
        }
160
161 4
        return (bool) preg_match('~^'.preg_quote($root, '~').'([/\?#].*)?$~i', $url);
162
    }
163
164
    /**
165
     * Remove scheme for an URL.
166
     *
167
     * @param  string  $url
168
     * @return string
169
     */
170 4
    protected function removeScheme($url)
171
    {
172 4
        return preg_replace('#^https?://#i', '', $url);
173
    }
174
175
    /**
176
     * Generate an absolute URL to a path for the given application.
177
     *
178
     * @param  string  $app
179
     * @param  string  $path
180
     * @param  mixed  $parameters
181
     * @return string
182
     */
183 1
    public function url($app = '', $path = '', $parameters = [])
184
    {
185 1
        return $this->root($app).$this->stringAfter(
186 1
            $this->container['url']->to($path, $parameters),
187 1
            $this->container['url']->to('')
188
        );
189
    }
190
191
    /**
192
     * Return the remainder of a string after a given value.
193
     *
194
     * @param  string  $subject
195
     * @param  string  $search
196
     * @return string
197
     */
198 1
    protected function stringAfter($subject, $search)
199
    {
200 1
        return $search === '' ? $subject : array_reverse(explode($search, $subject, 2))[0];
201
    }
202
203
    /**
204
     * Register routes for each sub application.
205
     * You may call this method in `RouteServiceProvider::map()`.
206
     *
207
     * @param  array  $attributes
208
     * @return void
209
     */
210 1
    public function routes(array $attributes = [])
211
    {
212 1
        foreach ($this->appUrls() as $id => $url) {
213 1
            if (! file_exists($file = base_path("routes/$id.php"))) {
214 1
                continue;
215
            }
216
217 1
            $this->container['router']->group(
218 1
                $this->getRouteGroupAttributes($id, Arr::get($attributes, $id, [])),
219 1
                function ($router) use ($file) {
220 1
                    require $file;
221 1
                }
222
            );
223
        }
224 1
    }
225
226
    /**
227
     * Get route group attributes for the given application.
228
     *
229
     * @param  string  $app
230
     * @param  array  $attributes
231
     * @return array
232
     */
233 1
    protected function getRouteGroupAttributes($app, array $attributes = [])
234
    {
235
        $attr = [
236 1
            'domain' => $this->domain($app),
237 1
            'middleware' => $this->container['router']->hasMiddlewareGroup($app) ? $app : 'web',
238 1
            'namespace' => $this->getRootControllerNamespace($app),
239
        ];
240
241 1
        if ($prefix = $this->prefix($app)) {
242 1
            $attr['prefix'] = $prefix;
243
        }
244
245 1
        return array_merge($attr, $attributes);
246
    }
247
248
    /**
249
     * Get the root controller namespace for the given application.
250
     *
251
     * @param  string  $app
252
     * @return string
253
     */
254 1
    protected function getRootControllerNamespace($app)
255
    {
256 1
        $namespace = $this->container['url']->getRootControllerNamespace()
257 1
            ?: $this->container->getNamespace().'Http\Controllers';
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Illuminate\Contracts\Container\Container as the method getNamespace() does only exist in the following implementations of said interface: Illuminate\Foundation\Application.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
258
259 1
        return trim($namespace.'\\'.Str::studly($app), '\\');
260
    }
261
262
    /**
263
     * Register macros.
264
     *
265
     * @param  \Illuminate\Contracts\Container\Container  $container
266
     * @return void
267
     */
268 3
    public static function registerMacros(Container $container)
269
    {
270 3
        static::registerMacro(
271 3
            $container['url'],
272 3
            'getRootControllerNamespace',
273 3
            function () {
274 2
                return $this->rootNamespace;
0 ignored issues
show
Bug introduced by
The property rootNamespace does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
275 3
            }
276
        );
277
278 3
        static::registerMacro(
279 3
            $container['router'],
280 3
            'hasMiddlewareGroup',
281 3
            function ($name) {
282
                return array_key_exists($name, $this->middlewareGroups);
0 ignored issues
show
Bug introduced by
The property middlewareGroups does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
283 3
            }
284
        );
285 3
    }
286
287
    /**
288
     * Register a macro to the class.
289
     *
290
     * @param  string|object  $class
291
     * @param  string  $method
292
     * @param  object|callable  $macro
293
     * @return void
294
     */
295 3
    protected static function registerMacro($class, $method, $macro)
296
    {
297 3
        if (! method_exists($class, $method)) {
298 3
            $class = is_object($class) ? get_class($class) : $class;
299
300 3
            call_user_func_array([$class, 'macro'], [$method, $macro]);
301
        }
302 3
    }
303
}
304