Completed
Pull Request — master (#5)
by
unknown
01:48
created

OverloadClass   F

Complexity

Total Complexity 67

Size/Duplication

Total Lines 321
Duplicated Lines 6.85 %

Coupling/Cohesion

Components 1
Dependencies 0

Importance

Changes 0
Metric Value
wmc 67
lcom 1
cbo 0
dl 22
loc 321
rs 3.04
c 0
b 0
f 0

12 Methods

Rating   Name   Duplication   Size   Complexity  
A overload() 0 5 1
B defineAutoloadExcludeFromClassmap() 14 27 9
B defineAutoloadFiles() 0 46 9
A getAutoload() 0 12 3
A createDirectories() 0 14 4
A generateProxy() 0 21 1
D getPhpForDuplicatedFile() 8 68 20
A assertOnlyRightClassFound() 0 11 3
A replaceNamespace() 0 8 1
B getClassNameFromTokens() 0 30 9
A addUse() 0 15 5
A addUsesInPhpLines() 0 11 2

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like OverloadClass often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use OverloadClass, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace steevanb\ComposerOverloadClass;
4
5
use Composer\Script\Event;
6
use Composer\IO\IOInterface;
7
8
class OverloadClass
9
{
10
    const EXTRA_OVERLOAD_CACHE_DIR = 'composer-overload-cache-dir';
11
    const EXTRA_OVERLOAD_CACHE_DIR_DEV = 'composer-overload-cache-dir-dev';
12
    const EXTRA_OVERLOAD_CLASS = 'composer-overload-class';
13
    const EXTRA_OVERLOAD_CLASS_DEV = 'composer-overload-class-dev';
14
    const EXTRA_OVERLOAD_DUPLICATE_ORIGINAL_FILE = 'duplicate-original-file';
15
    const NAMESPACE_PREFIX = 'ComposerOverloadClass';
16
17
    public static function overload(Event $event)
18
    {
19
        static::defineAutoloadExcludeFromClassmap($event);
20
        static::defineAutoloadFiles($event);
21
    }
22
23
    protected static function defineAutoloadExcludeFromClassmap(Event $event)
24
    {
25
        $originalFiles = ($event->isDevMode())
26
            ? [static::EXTRA_OVERLOAD_CLASS, static::EXTRA_OVERLOAD_CLASS_DEV]
27
            : [static::EXTRA_OVERLOAD_CLASS];
28
        $overloadFiles = ($event->isDevMode()) ? [] : [static::EXTRA_OVERLOAD_CLASS_DEV];
29
        $autoload = static::getAutoload($event);
30
        $extra = $event->getComposer()->getPackage()->getExtra();
31
32 View Code Duplication
        foreach ($originalFiles as $env) {
33
            if (array_key_exists($env, $extra)) {
34
                foreach ($extra[$env] as $className => $infos) {
35
                    $autoload['exclude-from-classmap'][] = $infos['original-file'];
36
                }
37
            }
38
        }
39
40 View Code Duplication
        foreach ($overloadFiles as $env) {
41
            if (array_key_exists($env, $extra)) {
42
                foreach ($extra[$env] as $className => $infos) {
43
                    $autoload['exclude-from-classmap'][] = $infos['overload-file'];
44
                }
45
            }
46
        }
47
48
        $event->getComposer()->getPackage()->setAutoload($autoload);
49
    }
50
51
    protected static function defineAutoloadFiles(Event $event)
52
    {
53
        $extra = $event->getComposer()->getPackage()->getExtra();
54
55
        if ($event->isDevMode()) {
56
            $envs = [static::EXTRA_OVERLOAD_CLASS, static::EXTRA_OVERLOAD_CLASS_DEV];
57
            $cacheDirKey = static::EXTRA_OVERLOAD_CACHE_DIR_DEV;
58
            if (array_key_exists($cacheDirKey, $extra) === false) {
59
                $cacheDirKey = static::EXTRA_OVERLOAD_CACHE_DIR;
60
            }
61
        } else {
62
            $envs = [static::EXTRA_OVERLOAD_CLASS];
63
            $cacheDirKey = static::EXTRA_OVERLOAD_CACHE_DIR;
64
        }
65
        if (array_key_exists($cacheDirKey, $extra) === false) {
66
            throw new \Exception('You must specify extra/' . $cacheDirKey . ' in composer.json');
67
        }
68
        $cacheDir = $extra[$cacheDirKey];
69
70
        $autoload = static::getAutoload($event);
71
72
        foreach ($envs as $extraKey) {
73
            if (array_key_exists($extraKey, $extra)) {
74
                foreach ($extra[$extraKey] as $className => $infos) {
75
                    if (
76
                        array_key_exists(static::EXTRA_OVERLOAD_DUPLICATE_ORIGINAL_FILE, $infos) === false
77
                        || $infos[static::EXTRA_OVERLOAD_DUPLICATE_ORIGINAL_FILE] === false
78
                    ) {
79
                        static::generateProxy(
80
                            $cacheDir,
81
                            $className,
82
                            $infos['original-file'],
83
                            $event->getIO()
84
                        );
85
                    }
86
                    $autoload['files'][$className] = $infos['overload-file'];
87
88
                    $message = '<info>' . $infos['original-file'] . '</info>';
89
                    $message .= ' is overloaded by <comment>' . $infos['overload-file'] . '</comment>';
90
                    $event->getIO()->write($message, true, IOInterface::VERBOSE);
91
                }
92
            }
93
        }
94
95
        $event->getComposer()->getPackage()->setAutoload($autoload);
96
    }
97
98
    /** @return array */
99
    protected static function getAutoload(Event $event)
100
    {
101
        $return = $event->getComposer()->getPackage()->getAutoload();
102
        if (array_key_exists('files', $return) === false) {
103
            $return['files'] = array();
104
        }
105
        if (array_key_exists('exclude-from-classmap', $return) === false) {
106
            $return['exclude-from-classmap'] = array();
107
        }
108
109
        return $return;
110
    }
111
112
    /** @param string $path */
113
    protected static function createDirectories($path, IOInterface $io)
114
    {
115
        if (is_dir($path) === false) {
116
            $io->write('Creating directory <info>' . $path . '</info>.', true, IOInterface::VERBOSE);
117
118
            $createdPath = null;
119
            foreach (explode('/', $path) as $directory) {
120
                if (is_dir($createdPath . $directory) === false) {
121
                    mkdir($createdPath . $directory);
122
                }
123
                $createdPath .= $directory . '/';
124
            }
125
        }
126
    }
127
128
    /**
129
     * @param string $cacheDir
130
     * @param string $fullyQualifiedClassName
131
     * @param string $filePath
132
     * @return string
133
     */
134
    protected static function generateProxy($cacheDir, $fullyQualifiedClassName, $filePath, IOInterface $io)
135
    {
136
        $php = static::getPhpForDuplicatedFile($filePath, $fullyQualifiedClassName);
137
        $classNameParts = array_merge(array(static::NAMESPACE_PREFIX), explode('\\', $fullyQualifiedClassName));
138
        array_pop($classNameParts);
139
        $finalCacheDir = $cacheDir . '/' . implode('/', $classNameParts);
140
        static::createDirectories($finalCacheDir, $io);
141
142
        $overloadedFilePath = $finalCacheDir . '/' . basename($filePath);
143
        file_put_contents($overloadedFilePath, $php);
144
145
        $io->write(
146
            'Generate proxy for <info>'
147
                . $fullyQualifiedClassName
148
                . '</info> in <comment>'
149
                . $overloadedFilePath
150
                . '</comment>',
151
            true,
152
            IOInterface::VERBOSE
153
        );
154
    }
155
156
    /**
157
     * @param string $filePath
158
     * @param string $fullyQualifiedClassName
159
     * @return string
160
     */
161
    protected static function getPhpForDuplicatedFile($filePath, $fullyQualifiedClassName)
162
    {
163
        if (is_readable($filePath) === false) {
164
            throw new \Exception('File "' . $filePath . '" does not exists, or is not readable.');
165
        }
166
167
        $phpLines = file($filePath);
168
        $namespace = substr($fullyQualifiedClassName, 0, strrpos($fullyQualifiedClassName, '\\'));
169
        $nextIsNamespace = false;
170
        $namespaceFound = null;
171
        $classesFound = [];
172
        $phpCodeForNamespace = null;
173
        $namespaceLine = null;
174
        $uses = [];
175
        $addUses = [];
176
        $isGlobalUse = true;
177
        $lastUseLine = null;
178
        $tokens = token_get_all(implode(null, $phpLines));
179
        foreach ($tokens as $index => $token) {
180
            if (is_array($token)) {
181
                if ($token[0] === T_NAMESPACE) {
182
                    $nextIsNamespace = true;
183
                    $namespaceLine = $token[2];
184 View Code Duplication
                } elseif ($isGlobalUse && $token[0] === T_CLASS) {
185
                    $classesFound[] = static::getClassNameFromTokens($tokens, $index + 1);
186
                    $isGlobalUse = false;
187
                } elseif ($token[0] === T_EXTENDS) {
188
                    static::addUse(static::getClassNameFromTokens($tokens, $index + 1), $namespaceFound, $uses, $addUses);
189
                } elseif ($isGlobalUse && $token[0] === T_USE) {
190
                    // What is the next token which is NOT a whitespace..
191
                    $walkForwardSteps = 1;
192
                    $continue = true;
193
                    do {
194
                        $nextNonWhitespaceToken = $tokens[$index + $walkForwardSteps];
195
                        if($nextNonWhitespaceToken[0] !== T_WHITESPACE){
196
                            $continue = false;
197
                        }
198
                        $walkForwardSteps++;
199
                    } while($continue);
200 View Code Duplication
                    if($nextNonWhitespaceToken[0] === T_STRING ) {
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...
201
                        $uses[]      = static::getClassNameFromTokens($tokens, $index + 1);
202
                        $lastUseLine = $token[2];
203
                    }
204
                }
205
206
                if ($nextIsNamespace) {
207
                    $phpCodeForNamespace .= $token[1];
208
                    if ($token[0] === T_NS_SEPARATOR || $token[0] === T_STRING) {
209
                        $namespaceFound .= $token[1];
210
                    }
211
                }
212
            } elseif ($nextIsNamespace && $token === ';') {
213
                $phpCodeForNamespace .= $token;
214
                if ($namespaceFound !== $namespace) {
215
                    $message = 'Expected namespace "' . $namespace . '", found "' . $namespaceFound . '" ';
216
                    $message .= 'in "' . $filePath . '".';
217
                    throw new \Exception($message);
218
                }
219
                $nextIsNamespace = false;
220
            }
221
        }
222
223
        static::assertOnlyRightClassFound($classesFound, $fullyQualifiedClassName, $filePath);
224
        static::replaceNamespace($namespaceFound, $phpCodeForNamespace, $phpLines, $namespaceLine);
225
        static::addUsesInPhpLines($addUses, $phpLines, ($lastUseLine === null ? $namespaceLine : $lastUseLine));
226
227
        return implode(null, $phpLines);
228
    }
229
230
    /**
231
     * @param string $fullyQualifiedClassName
232
     * @param string $filePath
233
     */
234
    protected static function assertOnlyRightClassFound(array $classFound, $fullyQualifiedClassName, $filePath)
235
    {
236
        $className = substr($fullyQualifiedClassName, strrpos($fullyQualifiedClassName, '\\') + 1);
237
        if (count($classFound) !== 1) {
238
            throw new \Exception('Expected 1 class, found "' . implode(', ', $classFound) . '" in "' . $filePath . '".');
239
        } elseif ($classFound[0] !== $className) {
240
            $message = 'Expected "' . $className . '" class, found "' . $classFound[0] . '" ';
241
            $message .= 'in "' . $filePath . '".';
242
            throw new \Exception($message);
243
        }
244
    }
245
246
    /**
247
     * @param string $namespace
248
     * @param string $phpCodeForNamespace
249
     * @param array $phpLines
250
     * @param int $namespaceLine
251
     */
252
    protected static function replaceNamespace($namespace, $phpCodeForNamespace, &$phpLines, $namespaceLine)
253
    {
254
        $phpLines[$namespaceLine - 1] = str_replace(
255
            $phpCodeForNamespace,
256
            'namespace ' . static::NAMESPACE_PREFIX . '\\' . $namespace . ';',
257
            $phpLines[$namespaceLine - 1]
258
        );
259
    }
260
261
    /**
262
     * @param int $index
263
     * @return string
264
     */
265
    protected static function getClassNameFromTokens(array &$tokens, $index)
266
    {
267
        $return = null;
268
        do {
269
            if (
270
                is_array($tokens[$index])
271
                && (
272
                    $tokens[$index][0] === T_STRING
273
                    || $tokens[$index][0] === T_NS_SEPARATOR
274
                )
275
            ) {
276
                $return .= $tokens[$index][1];
277
            }
278
279
            $index++;
280
            $continue =
281
                is_array($tokens[$index])
282
                && (
283
                    $tokens[$index][0] === T_STRING
284
                    || $tokens[$index][0] === T_NS_SEPARATOR
285
                    || $tokens[$index][0] === T_WHITESPACE
286
                );
287
        } while ($continue);
288
289
        if ($return === null) {
290
            throw new \Exception('Class not found in tokens.');
291
        }
292
293
        return $return;
294
    }
295
296
    /**
297
     * @param string $className
298
     * @param string $namespace
299
     */
300
    protected static function addUse($className, $namespace, array $uses, array &$addUses)
301
    {
302
        if (substr($className, 0, 1) !== '\\') {
303
            $alreadyInUses = false;
304
            foreach ($uses as $use) {
305
                if (substr($use, strrpos($use, '\\') + 1) === $className) {
306
                    $alreadyInUses = true;
307
                }
308
            }
309
310
            if ($alreadyInUses === false) {
311
                $addUses[] = $namespace . '\\' . $className;
312
            }
313
        }
314
    }
315
316
    /** @param int $line */
317
    protected static function addUsesInPhpLines(array $addUses, array &$phpLines, $line)
318
    {
319
        $linesBefore = ($line > 0) ? array_slice($phpLines, 0, $line) : [];
320
        $linesAfter = array_slice($phpLines, $line);
321
322
        array_walk($addUses, function(&$addUse) {
323
            $addUse = 'use ' . $addUse . ';' . "\n";
324
        });
325
326
        $phpLines = array_merge($linesBefore, $addUses, $linesAfter);
327
    }
328
}
329