Completed
Push — master ( 1a3dd2...a893e4 )
by Julius
02:37
created

ApiDocBuilder::setupReflection()   C

Complexity

Conditions 7
Paths 15

Size

Total Lines 32
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 7
eloc 21
c 0
b 0
f 0
nc 15
nop 0
dl 0
loc 32
rs 6.7272
1
<?php
2
/**
3
 * @copyright Copyright (c) 2017 Julius Härtl <[email protected]>
4
 *
5
 * @author Julius Härtl <[email protected]>
6
 *
7
 * @license GNU AGPL version 3 or any later version
8
 *
9
 *  This program is free software: you can redistribute it and/or modify
10
 *  it under the terms of the GNU Affero General Public License as
11
 *  published by the Free Software Foundation, either version 3 of the
12
 *  License, or (at your option) any later version.
13
 *
14
 *  This program is distributed in the hope that it will be useful,
15
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
16
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17
 *  GNU Affero General Public License for more details.
18
 *
19
 *  You should have received a copy of the GNU Affero General Public License
20
 *  along with this program. If not, see <http://www.gnu.org/licenses/>.
21
 *
22
 */
23
24
namespace JuliusHaertl\PHPDocToRst;
25
26
use JuliusHaertl\PHPDocToRst\Builder\TraitBuilder;
27
use phpDocumentor\Reflection\File\LocalFile;
28
use phpDocumentor\Reflection\Php\Namespace_;
29
use phpDocumentor\Reflection\Php\Project;
30
use phpDocumentor\Reflection\Php\ProjectFactory;
31
use JuliusHaertl\PHPDocToRst\Builder\MainIndexBuilder;
32
use JuliusHaertl\PHPDocToRst\Builder\NamespaceIndexBuilder;
33
use JuliusHaertl\PHPDocToRst\Extension\Extension;
34
use JuliusHaertl\PHPDocToRst\Builder\ClassBuilder;
35
use JuliusHaertl\PHPDocToRst\Builder\InterfaceBuilder;
36
37
class ApiDocBuilder {
38
39
    /** @var Project */
40
    private $project;
41
42
    /** @var array */
43
    private $docFiles = [];
44
45
    /** @var Extension[] */
46
    private $extensions;
47
48
    /** @var string[] */
49
    private $extensionNames = [];
50
51
    /** @var string[] */
52
    private $srcDir;
53
54
    /** @var string */
55
    private $dstDir;
56
57
    /** @var bool */
58
    private $verboseOutput = false;
59
60
    /** @var bool */
61
    private $debugOutput = false;
62
63
    public function __construct($srcDir, $dstDir) {
64
        $this->dstDir = $dstDir;
65
        $this->srcDir = (array)$srcDir;
66
    }
67
68
    public function build() {
69
        $this->setupReflection();
70
        $this->createDirectoryStructure();
71
        $this->parseFiles();
72
        $this->buildIndexes();
73
    }
74
75
    /* hacky logging for cli */
76
    public function setVerboseOutput($v) {
77
        $this->verboseOutput = $v;
78
    }
79
80
    public function setDebugOutput($v) {
81
        $this->debugOutput = $v;
82
    }
83
84
    public function log($message) {
85
        if ($this->verboseOutput) {
86
            echo $message . PHP_EOL;
87
        }
88
    }
89
90
    public function debug($message) {
91
        if ($this->debugOutput) {
92
            echo $message . PHP_EOL;
93
        }
94
    }
95
96
    /**
97
     * @throws \Exception
98
     */
99
    private function setupReflection() {
100
101
        $interfaceList = [];
102
        $this->log('Start parsing files.');
103
        foreach ($this->srcDir as $srcDir) {
104
            $dir = new \RecursiveDirectoryIterator($srcDir);
105
            $files = new \RecursiveIteratorIterator($dir);
106
107
            foreach ($files as $file) {
108
                if ($file->isDir()) {
109
                    continue;
110
                }
111
                try {
112
                    $interfaceList[] = new LocalFile($file->getPathname());
113
                } catch (\Exception $e) {
114
                    $this->log('Failed to load ' . $file->getPathname() . PHP_EOL);
115
                }
116
            }
117
        }
118
119
        $projectFactory = ProjectFactory::createInstance();
120
        $this->project = $projectFactory->create('MyProject', $interfaceList);
121
        $this->log('Successfully parsed files.');
122
123
        // load extensions
124
        foreach ($this->extensionNames as $extensionName) {
125
            $extension = new $extensionName($this->project);
126
            if (!is_subclass_of($extension, Extension::class)) {
127
                $this->log('Failed to load extension ' . get_class($extensionName) . '.');
0 ignored issues
show
Bug introduced by
$extensionName of type string is incompatible with the type object expected by parameter $object of get_class(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

127
                $this->log('Failed to load extension ' . get_class(/** @scrutinizer ignore-type */ $extensionName) . '.');
Loading history...
128
            }
129
            $this->extensions[] = $extension;
130
            $this->log('Extension ' . get_class($extensionName) . ' loaded.');
131
        }
132
    }
133
134
    /**
135
     * @param string $class name of the extension class
136
     * @throws \Exception
137
     */
138
    public function addExtension($class) {
139
        $this->extensionNames[] = $class;
140
    }
141
142
    /**
143
     * Create directory structure for the rst output
144
     * @throws \Exception
145
     */
146
    public function createDirectoryStructure() {
147
        foreach ($this->project->getNamespaces() as $namespace) {
148
            $namespaceDir = $this->dstDir . str_replace('\\', '/', $namespace->getFqsen());
149
            if (is_dir($namespaceDir)) {
150
                continue;
151
            }
152
            if (!mkdir($namespaceDir, 0755, true)) {
153
                throw new WriteException('Could not create directory ' . $namespaceDir);
154
            }
155
        }
156
    }
157
158
    public function parseFiles() {
159
        /** @var Extension $extension */
160
        foreach ($this->extensions as $extension) {
161
            $extension->prepare();
162
        }
163
        $this->log('Start building files');
164
        foreach ($this->project->getFiles() as $file) {
165
            /**
166
             * Go though interfaces/classes/functions of files and build documentation
167
             */
168 View Code Duplication
            foreach ($file->getInterfaces() as $interface) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
169
                $fqsen = $interface->getFqsen();
170
                $builder = new InterfaceBuilder($file, $interface, $this->extensions);
171
                $filename = $this->dstDir . str_replace('\\', '/', $fqsen) . '.rst';
172
                file_put_contents($filename, $builder->getContent());
173
                $this->docFiles[(string)$interface->getFqsen()] = str_replace('\\', '/', $fqsen);
174
                $this->debug('Written interface documentation to ' . $filename);
175
            }
176
177
            foreach ($file->getClasses() as $class) {
178
                $fqsen = $class->getFqsen();
179
                $builder = new ClassBuilder($file, $class, $this->extensions);
180
                $filename = $this->dstDir . str_replace('\\', '/', $fqsen) . '.rst';
181
                file_put_contents($filename, $builder->getContent());
182
                $this->docFiles[(string)$class->getFqsen()] = str_replace('\\', '/', $fqsen);
183
184
                // also build root namespace in indexes
185
                if (strpos((string)substr($class->getFqsen(), 1), '\\') === false) {
186
                    $this->project->getRootNamespace()->addClass($class->getFqsen());
187
                }
188
                $this->debug('Written class documentation to ' . $filename);
189
            }
190
191 View Code Duplication
            foreach ($file->getTraits() as $trait) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
192
                $fqsen = $trait->getFqsen();
193
                $builder = new TraitBuilder($file, $trait, $this->extensions);
194
                $filename = $this->dstDir . str_replace('\\', '/', $fqsen) . '.rst';
195
                file_put_contents($filename, $builder->getContent());
196
                $this->docFiles[(string)$trait->getFqsen()] = str_replace('\\', '/', $fqsen);
197
                $this->debug('Written trait documentation to ' . $filename);
198
            }
199
200
            // TODO: document constants/functions without namespace
201
            // $file->getConstants();
202
            // $file->getFunctions();
203
204
205
        }
206
    }
207
208
    public function buildIndexes() {
209
        $this->log('Build indexes.');
210
        $namespaces = $this->project->getNamespaces();
211
        $namespaces['\\'] = $this->project->getRootNamespace();
212
        usort($namespaces, function (Namespace_ $a, Namespace_ $b) {
213
            return strcmp($a->getFqsen(), $b->getFqsen());
214
        });
215
        /** @var Namespace_ $namespace */
216
        foreach ($namespaces as $namespace) {
217
            $this->debug('Build namespace index for ' . $namespace->getFqsen());
218
            $builder = new NamespaceIndexBuilder($namespaces, $namespace);
219
            $builder->render();
220
            $path = $this->dstDir . str_replace('\\', '/', $namespace->getFqsen()) . '/index.rst';
221
            file_put_contents($path, $builder->getContent());
222
        }
223
224
        $this->log('Build main index files.');
225
        $builder = new MainIndexBuilder($namespaces);
226
        $builder->render();
227
        $path = $this->dstDir . '/index-namespaces-all.rst';
228
        file_put_contents($path, $builder->getContent());
229
    }
230
}
231