Completed
Pull Request — master (#2)
by Randall
04:53
created

FileRepository::addLocation()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 2
c 1
b 0
f 0
dl 0
loc 5
rs 10
cc 1
nc 1
nop 1
1
<?php
2
3
namespace Rawilk\LaravelModules;
4
5
use Countable;
6
use Illuminate\Contracts\Container\Container;
7
use Illuminate\Filesystem\Filesystem;
8
use Illuminate\Support\Arr;
9
use Illuminate\Support\Str;
10
use Illuminate\Support\Traits\Macroable;
11
use Rawilk\LaravelModules\Contracts\Repository;
12
use Rawilk\LaravelModules\Exceptions\InvalidAssetPath;
13
use Rawilk\LaravelModules\Exceptions\ModuleNotFound;
14
use Rawilk\LaravelModules\Process\Installer;
15
use Rawilk\LaravelModules\Process\Updater;
16
use Symfony\Component\Process\Process;
17
18
abstract class FileRepository implements Repository, Countable
19
{
20
    use Macroable;
0 ignored issues
show
Bug introduced by
The trait Illuminate\Support\Traits\Macroable requires the property $name which is not provided by Rawilk\LaravelModules\FileRepository.
Loading history...
21
22
    /** @var \Illuminate\Contracts\Foundation\Application */
23
    protected $app;
24
25
    /** @var \Illuminate\Cache\CacheManager */
26
    private $cache;
27
28
    /** @var \Illuminate\Contracts\Config\Repository */
29
    private $config;
30
31
    /** @var \Illuminate\Filesystem\Filesystem */
32
    private $files;
33
34
    /** @var bool */
35
    private $onlyCustomPaths = false;
36
37
    /** @var string */
38
    protected $path;
39
40
    /** @var array */
41
    protected $paths = [];
42
43
    /** @var string */
44
    protected $stubPath;
45
46
    /** @var \Illuminate\Contracts\Routing\UrlGenerator */
47
    private $url;
48
49
    /**
50
     * @param \Illuminate\Contracts\Container\Container $app
51
     * @param string|null $path
52
     */
53
    public function __construct(Container $app, ?string $path = null)
54
    {
55
        $this->app = $app;
0 ignored issues
show
Documentation Bug introduced by
$app is of type Illuminate\Contracts\Container\Container, but the property $app was declared to be of type Illuminate\Contracts\Foundation\Application. Are you sure that you always receive this specific sub-class here, or does it make sense to add an instanceof 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 given class or a super-class is assigned to a property that is type hinted more strictly.

Either this assignment is in error or an instanceof check should be added for that assignment.

class Alien {}

class Dalek extends Alien {}

class Plot
{
    /** @var  Dalek */
    public $villain;
}

$alien = new Alien();
$plot = new Plot();
if ($alien instanceof Dalek) {
    $plot->villain = $alien;
}
Loading history...
56
        $this->path = $path;
57
        $this->url = $app['url'];
58
        $this->config = $app['config'];
59
        $this->files = $app['files'];
60
        $this->cache = $app['cache'];
61
    }
62
63
    abstract protected function createModule(...$args): Module;
64
65
    public function addLocation(string $path): self
66
    {
67
        $this->paths[] = $path;
68
69
        return $this;
70
    }
71
72
    public function all(): array
73
    {
74
        if (! $this->config('cache.enabled')) {
75
            return $this->scan();
76
        }
77
78
        return $this->formatCached($this->getCached());
79
    }
80
81
    public function allDisabled(): array
82
    {
83
        return $this->getByStatus(false);
84
    }
85
86
    public function allEnabled(): array
87
    {
88
        return $this->getByStatus(true);
89
    }
90
91
    public function asset(string $asset): string
92
    {
93
        if (! Str::contains($asset, ':')) {
94
            throw InvalidAssetPath::missingModuleName($asset);
95
        }
96
97
        [$name, $url] = explode(':', $asset);
98
99
        $baseUrl = str_replace(public_path() . DIRECTORY_SEPARATOR, '', $this->getAssetsPath());
100
101
        $url = $this->url->asset("{$baseUrl}/{$name}/{$url}");
102
103
        return str_replace(['http://', 'https://'], '//', $url);
104
    }
105
106
    public function assetPath(string $name): string
107
    {
108
        return $this->config('paths.assets') . '/' . $name;
109
    }
110
111
    public function boot(): void
112
    {
113
        /** @var \Rawilk\LaravelModules\Module $module */
114
        foreach ($this->getOrdered() as $module) {
115
            $module->boot();
116
        }
117
    }
118
119
    public function config(string $key, $default = null)
120
    {
121
        return $this->config->get("modules.{$key}", $default);
122
    }
123
124
    public function collections(bool $status = true): Collection
125
    {
126
        return new Collection($this->getByStatus($status));
127
    }
128
129
    public function count(): int
130
    {
131
        return count($this->all());
132
    }
133
134
    public function delete(string $name): bool
135
    {
136
        return $this->findOrFail($name)->delete();
137
    }
138
139
    public function disable(string $name): void
140
    {
141
        $this->findOrFail($name)->disable();
142
    }
143
144
    public function enable(string $name): void
145
    {
146
        $this->findOrFail($name)->enable();
147
    }
148
149
    public function find(string $name): ?Module
150
    {
151
        /** @var \Rawilk\LaravelModules\Module $module */
152
        foreach ($this->all() as $module) {
153
            if ($module->getLowerName() === strtolower($name)) {
154
                return $module;
155
            }
156
        }
157
158
        return null;
159
    }
160
161
    public function findByAlias(string $alias): ?Module
162
    {
163
        /** @var \Rawilk\LaravelModules\Module $module */
164
        foreach ($this->all() as $module) {
165
            if ($module->getAlias() === $alias) {
166
                return $module;
167
            }
168
        }
169
170
        return null;
171
    }
172
173
    public function findOrFail(string $name): Module
174
    {
175
        $module = $this->find($name);
176
177
        if ($module !== null) {
178
            return $module;
179
        }
180
181
        throw ModuleNotFound::named($name);
182
    }
183
184
    public function findRequirements(string $name): array
185
    {
186
        $requirements = [];
187
188
        $module = $this->findOrFail($name);
189
190
        foreach ($module->getRequires() as $requirementName) {
191
            $requirements[] = $this->findByAlias($requirementName);
192
        }
193
194
        return $requirements;
195
    }
196
197
    /**
198
     * Forget modules used in a cli session.
199
     */
200
    public function forgetUsed(): void
201
    {
202
        $usedStoragePath = $this->getUsedStoragePath();
203
204
        if ($this->getFiles()->exists($usedStoragePath)) {
205
            $this->getFiles()->delete($usedStoragePath);
206
        }
207
    }
208
209
    public function getAssetsPath(): string
210
    {
211
        return $this->config('paths.assets');
212
    }
213
214
    public function getByStatus(bool $active): array
215
    {
216
        $modules = [];
217
218
        /** @var \Rawilk\LaravelModules\Module $module */
219
        foreach ($this->all() as $name => $module) {
220
            if ($module->isStatus($active)) {
221
                $modules[$name] = $module;
222
            }
223
        }
224
225
        // Reset our variable
226
        $this->onlyCustomPaths = false;
227
228
        return $modules;
229
    }
230
231
    public function getCached(): array
232
    {
233
        return $this->cache->remember($this->config('cache.key'), $this->config('cache.lifetime'), function () {
234
            return $this->toCollection()->toArray();
235
        });
236
    }
237
238
    public function getFiles(): Filesystem
239
    {
240
        return $this->files;
241
    }
242
243
    public function getModulePath(string $name): string
244
    {
245
        try {
246
            return $this->findOrFail($name)->getPath() . '/';
247
        } catch (ModuleNotFound $e) {
248
            return $this->getPath() . '/' . Str::studly($name) . '/';
249
        }
250
    }
251
252
    public function getOrdered(string $direction = 'asc'): array
253
    {
254
        $modules = $this->allEnabled();
255
256
        uasort($modules, static function (Module $a, Module $b) use ($direction) {
257
            $aOrder = $a->get('order');
258
            $bOrder = $b->get('order');
259
260
            if ($aOrder === $bOrder) {
261
                return 0;
262
            }
263
264
            if ($direction === 'desc') {
265
                return $aOrder < $bOrder ? 1 : -1;
266
            }
267
268
            return $aOrder > $bOrder ? 1 : -1;
269
        });
270
271
        return $modules;
272
    }
273
274
    public function getPath(): string
275
    {
276
        return $this->path ?: $this->config('paths.modules', base_path('Modules'));
277
    }
278
279
    public function getPaths(): array
280
    {
281
        return $this->paths;
282
    }
283
284
    public function getScanPaths(): array
285
    {
286
        $paths = $this->paths;
287
288
        if (! $this->onlyCustomPaths) {
289
            $paths[] = $this->getPath();
290
        }
291
292
        if ($this->config('scan.enabled')) {
293
            $paths = array_merge($paths, $this->config('scan.paths'));
294
        }
295
296
        return array_map(static function ($path) {
297
            return Str::endsWith($path, '/*') ? $path : Str::finish($path, '/*');
298
        }, $paths);
299
    }
300
301
    public function getStubPath(): ?string
302
    {
303
        if ($this->stubPath !== null) {
304
            return $this->stubPath;
305
        }
306
307
        if ($this->config('stubs.enabled')) {
308
            return $this->config('stubs.path');
309
        }
310
311
        return $this->stubPath;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->stubPath returns the type void which is incompatible with the type-hinted return null|string.
Loading history...
312
    }
313
314
    /**
315
     * Get the module being used in a cli session.
316
     *
317
     * @return string
318
     */
319
    public function getUsedNow(): string
320
    {
321
        return $this->findOrFail($this->getFiles()->get($this->getUsedStoragePath()));
322
    }
323
324
    public function getUsedStoragePath(): string
325
    {
326
        $directory = storage_path('app/modules');
327
        if (! $this->getFiles()->exists($directory)) {
328
            $this->getFiles()->makeDirectory($directory, 0777, true);
329
        }
330
331
        $path = storage_path('app/modules/modules.used');
332
        if (! $this->getFiles()->exists($path)) {
333
            $this->getFiles()->put($path, '');
334
        }
335
336
        return $path;
337
    }
338
339
    /**
340
     * Retrieve a sorted list of registered view partials to render for the given module.
341
     *
342
     * @param string $moduleName
343
     * @return array
344
     * @throws \Illuminate\Contracts\Filesystem\FileNotFoundException
345
     */
346
    public function getViewPartials(string $moduleName): array
347
    {
348
        $modules = $this->getOrdered();
349
350
        $partials = [];
351
352
        /** @var \Rawilk\LaravelModules\Module $module */
353
        foreach ($modules as $module) {
354
            $path = $module->getExtraPath('config/module-views.php');
355
356
            if (! $this->files->exists($path)) {
357
                continue;
358
            }
359
360
            $moduleViews = $this->files->getRequire($path);
361
            $moduleViews = Arr::get($moduleViews, $moduleName, []);
362
363
            if (! empty($moduleViews)) {
364
                $partials = array_merge($partials, $moduleViews);
365
            }
366
        }
367
368
        uasort($partials, static function ($a, $b) {
369
            $aOrder = $a['order'] ?? 0;
370
            $bOrder = $b['order'] ?? 0;
371
372
            if ($aOrder === $bOrder) {
373
                return 0;
374
            }
375
376
            return $aOrder > $bOrder ? 1 : -1;
377
        });
378
379
        return $partials;
380
    }
381
382
    public function has(string $name): bool
383
    {
384
        return array_key_exists($name, $this->all());
385
    }
386
387
    public function install(string $name, ?string $version = 'dev-master', ?string $type = 'composer', bool $subtree = false): Process
388
    {
389
        return (new Installer($name, $version, $type, $subtree))->run();
390
    }
391
392
    public function isDisabled(string $name): bool
393
    {
394
        return $this->findOrFail($name)->isDisabled();
395
    }
396
397
    public function isEnabled(string $name): bool
398
    {
399
        return $this->findOrFail($name)->isEnabled();
400
    }
401
402
    public function onlyCustomPaths(): self
403
    {
404
        $this->onlyCustomPaths = true;
405
406
        return $this;
407
    }
408
409
    public function register(): void
410
    {
411
        /** @var \Rawilk\LaravelModules\Module $module */
412
        foreach ($this->getOrdered() as $module) {
413
            $module->register();
414
        }
415
    }
416
417
    public function setStubPath(string $path): self
418
    {
419
        $this->stubPath = $path;
420
421
        return $this;
422
    }
423
424
    /**
425
     * Set a module being used in a cli session.
426
     *
427
     * @param string $name
428
     */
429
    public function setUsed(string $name): void
430
    {
431
        $module = $this->findOrFail($name);
432
433
        $this->getFiles()->put($this->getUsedStoragePath(), $module);
434
    }
435
436
    public function scan(): array
437
    {
438
        $paths = $this->getScanPaths();
439
440
        $modules = [];
441
442
        foreach ($paths as $key => $path) {
443
            $manifests = $this->getFiles()->glob("{$path}/module.json");
444
445
            is_array($manifests) || $manifests = [];
446
447
            foreach ($manifests as $manifest) {
448
                $name = Json::make($manifest)->get('name');
449
450
                $modules[$name] = $this->createModule($this->app, $name, dirname($manifest));
451
            }
452
        }
453
454
        return $modules;
455
    }
456
457
    public function toCollection(): Collection
458
    {
459
        return new Collection($this->scan());
460
    }
461
462
    public function update(string $name): void
463
    {
464
        with(new Updater($this))->update($name);
465
    }
466
467
    protected function formatCached(array $cached): array
468
    {
469
        $modules = [];
470
471
        foreach ($cached as $name => $module) {
472
            $path = $module['path'];
473
474
            $modules[$name] = $this->createModule($this->app, $name, $path);
475
        }
476
477
        return $modules;
478
    }
479
}
480