Completed
Pull Request — master (#608)
by
unknown
12:02
created

GenerateDocumentation::isValidRoute()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 9
rs 9.9666
c 0
b 0
f 0
cc 3
nc 4
nop 1
1
<?php
2
3
namespace Mpociot\ApiDoc\Commands;
4
5
use ReflectionClass;
6
use ReflectionException;
7
use Illuminate\Routing\Route;
8
use Illuminate\Console\Command;
9
use Mpociot\ApiDoc\Tools\Flags;
10
use Mpociot\ApiDoc\Tools\Utils;
11
use Mpociot\Reflection\DocBlock;
12
use Illuminate\Support\Collection;
13
use Mpociot\ApiDoc\Writing\Writer;
14
use Illuminate\Support\Facades\URL;
15
use Mpociot\ApiDoc\Extracting\Generator;
16
use Mpociot\ApiDoc\Matching\RouteMatcher;
17
use Mpociot\ApiDoc\Tools\DocumentationConfig;
18
19
class GenerateDocumentation extends Command
20
{
21
    /**
22
     * The name and signature of the console command.
23
     *
24
     * @var string
25
     */
26
    protected $signature = 'apidoc:generate
27
                            {--force : Force rewriting of existing routes}
28
    ';
29
30
    /**
31
     * The console command description.
32
     *
33
     * @var string
34
     */
35
    protected $description = 'Generate your API documentation from existing Laravel routes.';
36
37
    /**
38
     * @var DocumentationConfig
39
     */
40
    private $docConfig;
41
42
    /**
43
     * @var string
44
     */
45
    private $baseUrl;
46
47
    public function __construct()
48
    {
49
        parent::__construct();
50
    }
51
52
    /**
53
     * Execute the console command.
54
     *
55
     * @return void
56
     */
57
    public function handle()
58
    {
59
        // Using a global static variable here, so fuck off if you don't like it.
60
        // Also, the --verbose option is included with all Artisan commands.
61
        Flags::$shouldBeVerbose = $this->option('verbose');
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->option('verbose') can also be of type array or string. However, the property $shouldBeVerbose is declared as type boolean. 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...
62
63
        $this->docConfig = new DocumentationConfig(config('apidoc'));
64
        $this->baseUrl = $this->docConfig->get('base_url') ?? config('app.url');
65
66
        URL::forceRootUrl($this->baseUrl);
67
68
        $routeMatcher = new RouteMatcher($this->docConfig->get('routes'), $this->docConfig->get('router'));
69
        $routes = $routeMatcher->getRoutes();
70
71
        $generator = new Generator($this->docConfig);
72
        $parsedRoutes = $this->processRoutes($generator, $routes);
73
74
        $groupedRoutes = collect($parsedRoutes)
75
            ->groupBy('metadata.groupName')
76
            ->sortBy(static function ($group) {
77
                /* @var $group Collection */
78
                return $group->first()['metadata']['groupName'];
79
            }, SORT_NATURAL);
80
        $writer = new Writer(
81
            $groupedRoutes,
82
            $this->option('force'),
83
            $this,
84
            $this->docConfig
85
        );
86
        $writer->writeDocs();
87
    }
88
89
    /**
90
     * @param \Mpociot\ApiDoc\Extracting\Generator $generator
91
     * @param array $routes
92
     *
93
     * @return array
94
     */
95
    private function processRoutes(Generator $generator, array $routes)
96
    {
97
        $parsedRoutes = [];
98
        foreach ($routes as $routeItem) {
99
            $route = $routeItem['route'];
100
            /** @var Route $route */
101
            if ($this->isValidRoute($route) && $this->isRouteVisibleForDocumentation($route->getAction())) {
102
                $parsedRoutes[] = $generator->processRoute($route, $routeItem['apply'] ?? []);
103
                $this->info('Processed route: ['.implode(',', $generator->getMethods($route)).'] '.$generator->getUri($route));
104
            } else {
105
                $this->warn('Skipping route: ['.implode(',', $generator->getMethods($route)).'] '.$generator->getUri($route));
106
            }
107
        }
108
109
        return $parsedRoutes;
110
    }
111
112
    /**
113
     * @param Route $route
114
     *
115
     * @return bool
116
     */
117
    private function isValidRoute(Route $route)
118
    {
119
        $action = Utils::getRouteClassAndMethodNames($route->getAction());
120
        if (is_array($action)) {
121
            $action = implode('@', $action);
122
        }
123
124
        return ! is_callable($action) && ! is_null($action);
125
    }
126
127
    /**
128
     * @param array $action
129
     *
130
     * @throws ReflectionException
131
     *
132
     * @return bool
133
     */
134
    private function isRouteVisibleForDocumentation(array $action)
135
    {
136
        list($class, $method) = Utils::getRouteClassAndMethodNames($action);
137
        $reflection = new ReflectionClass($class);
138
139
        if (! $reflection->hasMethod($method)) {
140
            return false;
141
        }
142
143
        $comment = $reflection->getMethod($method)->getDocComment();
144
145
        if ($comment) {
146
            $phpdoc = new DocBlock($comment);
147
148
            return collect($phpdoc->getTags())
149
                ->filter(function ($tag) {
150
                    return $tag->getName() === 'hideFromAPIDocumentation';
151
                })
152
                ->isEmpty();
153
        }
154
155
        return true;
156
    }
157
}
158