Completed
Push — master ( 0ed20a...e8c824 )
by
unknown
10:05 queued 08:45
created

GenerateDocumentation::processRoutes()   A

Complexity

Conditions 5
Paths 5

Size

Total Lines 25

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 25
rs 9.2088
c 0
b 0
f 0
cc 5
nc 5
nop 2
1
<?php
2
3
namespace Mpociot\ApiDoc\Commands;
4
5
use Illuminate\Console\Command;
6
use Illuminate\Routing\Route;
7
use Illuminate\Support\Collection;
8
use Illuminate\Support\Facades\URL;
9
use Mpociot\ApiDoc\Extracting\Generator;
10
use Mpociot\ApiDoc\Matching\RouteMatcher;
11
use Mpociot\ApiDoc\Tools\DocumentationConfig;
12
use Mpociot\ApiDoc\Tools\Flags;
13
use Mpociot\ApiDoc\Tools\Utils;
14
use Mpociot\ApiDoc\Writing\Writer;
15
use Mpociot\Reflection\DocBlock;
16
use ReflectionClass;
17
use ReflectionException;
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');
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
            $messageFormat = '%s route: [%s] %s';
102
            $routeMethods = implode(',', $generator->getMethods($route));
103
            $routePath = $generator->getUri($route);
104
105
            if (! $this->isValidRoute($route) || ! $this->isRouteVisibleForDocumentation($route->getAction())) {
106
                $this->warn(sprintf($messageFormat, 'Skipping', $routeMethods, $routePath));
107
                continue;
108
            }
109
110
            try {
111
                $parsedRoutes[] = $generator->processRoute($route, $routeItem['apply'] ?? []);
112
                $this->info(sprintf($messageFormat, 'Processed', $routeMethods, $routePath));
113
            } catch (\Exception $exception) {
114
                $this->warn(sprintf($messageFormat, 'Skipping', $routeMethods, $routePath).' - '.$exception->getMessage());
115
            }
116
        }
117
118
        return $parsedRoutes;
119
    }
120
121
    /**
122
     * @param Route $route
123
     *
124
     * @return bool
125
     */
126
    private function isValidRoute(Route $route)
127
    {
128
        $action = Utils::getRouteClassAndMethodNames($route->getAction());
129
        if (is_array($action)) {
130
            $action = implode('@', $action);
131
        }
132
133
        return ! is_callable($action) && ! is_null($action);
134
    }
135
136
    /**
137
     * @param array $action
138
     *
139
     * @throws ReflectionException
140
     *
141
     * @return bool
142
     */
143
    private function isRouteVisibleForDocumentation(array $action)
144
    {
145
        list($class, $method) = Utils::getRouteClassAndMethodNames($action);
146
        $reflection = new ReflectionClass($class);
147
148
        if (! $reflection->hasMethod($method)) {
149
            return false;
150
        }
151
152
        $comment = $reflection->getMethod($method)->getDocComment();
153
154
        if ($comment) {
155
            $phpdoc = new DocBlock($comment);
156
157
            return collect($phpdoc->getTags())
158
                ->filter(function ($tag) {
159
                    return $tag->getName() === 'hideFromAPIDocumentation';
160
                })
161
                ->isEmpty();
162
        }
163
164
        return true;
165
    }
166
}
167