Completed
Pull Request — master (#672)
by Guy
02:09 queued 38s
created

GenerateDocumentation::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

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