Completed
Push — master ( b853d5...b8b655 )
by Marcel
02:59
created

GenerateDocumentation::processDingoRoutes()   B

Complexity

Conditions 5
Paths 3

Size

Total Lines 14
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 1 Features 1
Metric Value
c 2
b 1
f 1
dl 0
loc 14
rs 8.8571
cc 5
eloc 9
nc 3
nop 3
1
<?php
2
3
namespace Mpociot\ApiDoc\Commands;
4
5
use Illuminate\Console\Command;
6
use Illuminate\Support\Collection;
7
use Illuminate\Support\Facades\Route;
8
use Mpociot\ApiDoc\Generators\AbstractGenerator;
9
use Mpociot\ApiDoc\Generators\DingoGenerator;
10
use Mpociot\ApiDoc\Generators\LaravelGenerator;
11
use Mpociot\Documentarian\Documentarian;
12
13
class GenerateDocumentation extends Command
14
{
15
    /**
16
     * The name and signature of the console command.
17
     *
18
     * @var string
19
     */
20
    protected $signature = 'api:generate 
21
                            {--output=public/docs : The output path for the generated documentation}
22
                            {--routePrefix= : The route prefix to use for generation}
23
                            {--routes=* : The route names to use for generation}
24
                            {--actAsUserId= : The user ID to use for API response calls}
25
                            {--router=laravel : The router to be used (Laravel or Dingo)}
26
                            {--bindings= : Route Model Bindings}
27
    ';
28
29
    /**
30
     * The console command description.
31
     *
32
     * @var string
33
     */
34
    protected $description = 'Generate your API documentation from existing Laravel routes.';
35
36
    /**
37
     * Create a new command instance.
38
     *
39
     * @return void
0 ignored issues
show
Comprehensibility Best Practice introduced by
Adding a @return annotation to constructors is generally not recommended as a constructor does not have a meaningful return value.

Adding a @return annotation to a constructor is not recommended, since a constructor does not have a meaningful return value.

Please refer to the PHP core documentation on constructors.

Loading history...
40
     */
41
    public function __construct()
42
    {
43
        parent::__construct();
44
    }
45
46
    /**
47
     * Execute the console command.
48
     *
49
     * @return false|null
50
     */
51
    public function handle()
52
    {
53
        if ($this->option('router') === 'laravel') {
54
            $generator = new LaravelGenerator();
55
        } else {
56
            $generator = new DingoGenerator();
57
        }
58
59
        $allowedRoutes = $this->option('routes');
60
        $routePrefix = $this->option('routePrefix');
61
62
        $this->setUserToBeImpersonated($this->option('actAsUserId'));
63
64
        if ($routePrefix === null && ! count($allowedRoutes)) {
65
            $this->error('You must provide either a route prefix or a route to generate the documentation.');
66
67
            return false;
68
        }
69
70
        if ($this->option('router') === 'laravel') {
71
            $parsedRoutes = $this->processLaravelRoutes($generator, $allowedRoutes, $routePrefix);
72
        } else {
73
            $parsedRoutes = $this->processDingoRoutes($generator, $allowedRoutes, $routePrefix);
74
        }
75
        $parsedRoutes = collect($parsedRoutes)->sortBy('resource')->groupBy('resource');
76
77
        $this->writeMarkdown($parsedRoutes);
78
    }
79
80
    /**
81
     * @param  Collection $parsedRoutes
82
     *
83
     * @return void
84
     */
85
    private function writeMarkdown($parsedRoutes)
86
    {
87
        $outputPath = $this->option('output');
88
89
        $documentarian = new Documentarian();
90
91
        $markdown = view('apidoc::documentarian')->with('parsedRoutes', $parsedRoutes->all());
1 ignored issue
show
Bug introduced by
The method with does only exist in Illuminate\View\View, but not in Illuminate\Contracts\View\Factory.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
92
93
        if (! is_dir($outputPath)) {
94
            $documentarian->create($outputPath);
95
        }
96
97
        file_put_contents($outputPath.DIRECTORY_SEPARATOR.'source'.DIRECTORY_SEPARATOR.'index.md', $markdown);
98
99
        $this->info('Wrote index.md to: '.$outputPath);
100
101
        $this->info('Generating API HTML code');
102
103
        $documentarian->generate($outputPath);
104
105
        $this->info('Wrote HTML documentation to: '.$outputPath.'/public/index.html');
106
    }
107
108
    /**
109
     * @return array
110
     */
111
    private function getBindings()
112
    {
113
        $bindings = $this->option('bindings');
114
        if (empty($bindings)) {
115
            return [];
116
        }
117
        $bindings = explode('|', $bindings);
118
        $resultBindings = [];
119
        foreach ($bindings as $binding) {
120
            list($name, $id) = explode(',', $binding);
121
            $resultBindings[$name] = $id;
122
        }
123
124
        return $resultBindings;
125
    }
126
127
    /**
128
     * @param $actAs
129
     */
130
    private function setUserToBeImpersonated($actAs)
131
    {
132
        if (! empty($actAs)) {
133
            if (version_compare($this->laravel->version(), '5.2.0', '<')) {
134
                $userModel = config('auth.model');
135
                $user = $userModel::find($actAs);
136
                $this->laravel['auth']->setUser($user);
137
            } else {
138
                $userModel = config('auth.providers.users.model');
139
                $user = $userModel::find($actAs);
140
                $this->laravel['auth']->guard()->setUser($user);
141
            }
142
        }
143
    }
144
145
    /**
146
     * @return mixed
147
     */
148
    private function getRoutes()
149
    {
150
        if ($this->option('router') === 'laravel') {
151
            return Route::getRoutes();
152
        } else {
153
            return app('Dingo\Api\Routing\Router')->getRoutes()[$this->option('routePrefix')];
154
        }
155
    }
156
157
    /**
158
     * @param AbstractGenerator  $generator
159
     * @param $allowedRoutes
160
     * @param $routePrefix
161
     *
162
     * @return array
163
     */
164
    private function processLaravelRoutes(AbstractGenerator $generator, $allowedRoutes, $routePrefix)
165
    {
166
        $routes = $this->getRoutes();
167
        $bindings = $this->getBindings();
168
        $parsedRoutes = [];
169
        foreach ($routes as $route) {
170
            if (in_array($route->getName(), $allowedRoutes) || str_is($routePrefix, $route->getUri())) {
171
                if ($this->isValidRoute($route)) {
172
                    $parsedRoutes[] = $generator->processRoute($route, $bindings);
173
                    $this->info('Processed route: '.$route->getUri());
174
                } else {
175
                    $this->warn('Skipping route: '.$route->getUri().' - contains closure.');
176
                }
177
            }
178
        }
179
180
        return $parsedRoutes;
181
    }
182
183
    /**
184
     * @param AbstractGenerator $generator
185
     * @param $allowedRoutes
186
     * @param $routePrefix
187
     *
188
     * @return array
189
     */
190
    private function processDingoRoutes(AbstractGenerator $generator, $allowedRoutes, $routePrefix)
191
    {
192
        $routes = $this->getRoutes();
193
        $bindings = $this->getBindings();
194
        $parsedRoutes = [];
195
        foreach ($routes as $route) {
196
            if (empty($allowedRoutes) || in_array($route->getName(), $allowedRoutes) || str_is($routePrefix, $route->uri())) {
197
                $parsedRoutes[] = $generator->processRoute($route, $bindings);
198
                $this->info('Processed route: '.$route->uri());
199
            }
200
        }
201
202
        return $parsedRoutes;
203
    }
204
205
    /**
206
     * @param $route
207
     * @return bool
208
     */
209
    private function isValidRoute($route)
210
    {
211
        return !is_callable($route->getAction()['uses']);
212
    }
213
}
214