Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
98.86% covered (success)
98.86%
522 / 528
33.33% covered (danger)
33.33%
2 / 6
CRAP
0.00% covered (danger)
0.00%
0 / 1
FileScanner
98.86% covered (success)
98.86%
522 / 528
33.33% covered (danger)
33.33%
2 / 6
18
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
1
 findInFiles
80.00% covered (warning)
80.00%
4 / 5
0.00% covered (danger)
0.00%
0 / 1
3.07
 find
100.00% covered (success)
100.00%
23 / 23
100.00% covered (success)
100.00%
1 / 1
6
 addDiscoveredClassChange
66.67% covered (warning)
66.67%
4 / 6
0.00% covered (danger)
0.00%
0 / 1
3.33
 addDiscoveredNamespaceChange
60.00% covered (warning)
60.00%
3 / 5
0.00% covered (danger)
0.00%
0 / 1
3.58
 getBuiltIns
99.79% covered (success)
99.79%
481 / 482
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2/**
3 * The purpose of this class is only to find changes that should be made.
4 * i.e. classes and namespaces to change.
5 * Those recorded are updated in a later step.
6 */
7
8namespace BrianHenryIE\Strauss;
9
10use BrianHenryIE\Strauss\Composer\Extra\StraussConfig;
11use BrianHenryIE\Strauss\Types\ClassSymbol;
12use BrianHenryIE\Strauss\Types\ConstantSymbol;
13use BrianHenryIE\Strauss\Types\NamespaceSymbol;
14
15class FileScanner
16{
17
18    protected string $namespacePrefix;
19    protected string $classmapPrefix;
20
21    /** @var string[]  */
22    protected array $excludeNamespacesFromPrefixing = array();
23
24    /** @var string[]  */
25    protected array $excludeFilePatternsFromPrefixing = array();
26
27    /** @var string[]  */
28    protected array $namespaceReplacementPatterns = array();
29
30    protected DiscoveredSymbols $discoveredSymbols;
31
32    /**
33     * @var string[]
34     */
35    protected array $excludePackagesFromPrefixing;
36
37    /**
38     * FileScanner constructor.
39     * @param \BrianHenryIE\Strauss\Composer\Extra\StraussConfig $config
40     */
41    public function __construct(StraussConfig $config)
42    {
43        $this->discoveredSymbols = new DiscoveredSymbols();
44
45        $this->namespacePrefix = $config->getNamespacePrefix();
46        $this->classmapPrefix = $config->getClassmapPrefix();
47
48        $this->excludePackagesFromPrefixing = $config->getExcludePackagesFromPrefixing();
49        $this->excludeNamespacesFromPrefixing = $config->getExcludeNamespacesFromPrefixing();
50        $this->excludeFilePatternsFromPrefixing = $config->getExcludeFilePatternsFromPrefixing();
51
52        $this->namespaceReplacementPatterns = $config->getNamespaceReplacementPatterns();
53    }
54
55    /**
56     * @param DiscoveredFiles $files
57     */
58    public function findInFiles(DiscoveredFiles $files): DiscoveredSymbols
59    {
60        foreach ($files->getFiles() as $file) {
61            if (!$file->isPhpFile()) {
62                continue;
63            }
64
65            $this->find($file->getContents(), $file);
66        }
67
68        return $this->discoveredSymbols;
69    }
70
71
72    /**
73     * TODO: Don't use preg_replace_callback!
74     *
75     * @uses self::addDiscoveredNamespaceChange()
76     * @uses self::addDiscoveredClassChange()
77     *
78     * @param string $contents
79     */
80    protected function find(string $contents, File $file): void
81    {
82        // If the entire file is under one namespace, all we want is the namespace.
83        // If there were more than one namespace, it would appear as `namespace MyNamespace { ...`,
84        // a file with only a single namespace will appear as `namespace MyNamespace;`.
85        $singleNamespacePattern = '/
86            (<?php|\r\n|\n)                                              # A new line or the beginning of the file.
87            \s*                                                          # Allow whitespace before
88            namespace\s+(?<namespace>[0-9A-Za-z_\x7f-\xff\\\\]+)[\s\S]*; # Match a single namespace in the file.
89        /x'; //  # x: ignore whitespace in regex.
90        if (1 === preg_match($singleNamespacePattern, $contents, $matches)) {
91            $this->addDiscoveredNamespaceChange($matches['namespace'], $file);
92            return;
93        }
94
95        if (0 < preg_match_all('/\s*define\s*\(\s*["\']([^"\']*)["\']\s*,\s*["\'][^"\']*["\']\s*\)\s*;/', $contents, $constants)) {
96            foreach ($constants[1] as $constant) {
97                $constantObj = new ConstantSymbol($constant, $file);
98                $this->discoveredSymbols->add($constantObj);
99            }
100        }
101
102        // TODO traits
103
104        // TODO: Is the ";" in this still correct since it's being taken care of in the regex just above?
105        // Looks like with the preceding regex, it will never match.
106
107
108        preg_replace_callback(
109            '
110            ~                                            # Start the pattern
111                [\r\n]+\s*namespace\s+([a-zA-Z0-9_\x7f-\xff\\\\]+)[;{\s\n]{1}[\s\S]*?(?=namespace|$) 
112                                                        # Look for a preceding namespace declaration, 
113                                                        # followed by a semicolon, open curly bracket, space or new line
114                                                        # up until a 
115                                                        # potential second namespace declaration or end of file.
116                                                        # if found, match that much before continuing the search on
117                |                                        # the remainder of the string.
118                \/\*[\s\S]*?\*\/ |                      # Skip multiline comments
119                ^\s*\/\/.*$    |                           # Skip single line comments
120                \s*                                        # Whitespace is allowed before 
121                (?:abstract\sclass|class|interface)\s+    # Look behind for class, abstract class, interface
122                ([a-zA-Z0-9_\x7f-\xff]+)                # Match the word until the first non-classname-valid character
123                \s?                                        # Allow a space after
124                (?:{|extends|implements|\n|$)            # Class declaration can be followed by {, extends, implements 
125                                                        # or a new line
126            ~x', //                                     # x: ignore whitespace in regex.
127            function ($matches) use ($file) {
128
129                // If we're inside a namespace other than the global namespace:
130                if (1 === preg_match('/^\s*namespace\s+[a-zA-Z0-9_\x7f-\xff\\\\]+[;{\s\n]{1}.*/', $matches[0])) {
131                    $this->addDiscoveredNamespaceChange($matches[1], $file);
132
133                    return $matches[0];
134                }
135
136                if (count($matches) < 3) {
137                    return $matches[0];
138                }
139
140                // TODO: Why is this [2] and not [1] (which seems to be always empty).
141                $this->addDiscoveredClassChange($matches[2], $file);
142
143                return $matches[0];
144            },
145            $contents
146        );
147    }
148
149    protected function addDiscoveredClassChange(string $classname, File $file): void
150    {
151
152        if ('ReturnTypeWillChange' === $classname) {
153            return;
154        }
155        if (in_array($classname, $this->getBuiltIns())) {
156            return;
157        }
158
159        $classSymbol = new ClassSymbol($classname, $file);
160        $this->discoveredSymbols->add($classSymbol);
161    }
162
163    protected function addDiscoveredNamespaceChange(string $namespace, File $file): void
164    {
165
166        foreach ($this->excludeNamespacesFromPrefixing as $excludeNamespace) {
167            if (0 === strpos($namespace, $excludeNamespace)) {
168                return;
169            }
170        }
171
172        $namespaceObj = new NamespaceSymbol($namespace, $file);
173        $this->discoveredSymbols->add($namespaceObj);
174    }
175
176    /**
177     * Get a list of PHP built-in classes etc. so they are not prefixed.
178     *
179     * Polyfilled classes were being prefixed, but the polyfills are only active when the PHP version is below X,
180     * so calls to those prefixed polyfilled classnames would fail on newer PHP versions.
181     *
182     * NB: This list is not exhaustive. Any unloaded PHP extensions are not included.
183     *
184     * @see https://github.com/BrianHenryIE/strauss/issues/79
185     *
186     * ```
187     * array_filter(
188     *   get_declared_classes(),
189     *   function(string $className): bool {
190     *     $reflector = new \ReflectionClass($className);
191     *     return empty($reflector->getFileName());
192     *   }
193     * );
194     * ```
195     *
196     * @return string[]
197     */
198    protected function getBuiltIns(): array
199    {
200        $builtins = [
201                '7.4' =>
202                    [
203                        'classes' =>
204                            [
205                                'AppendIterator',
206                                'ArgumentCountError',
207                                'ArithmeticError',
208                                'ArrayIterator',
209                                'ArrayObject',
210                                'AssertionError',
211                                'BadFunctionCallException',
212                                'BadMethodCallException',
213                                'CURLFile',
214                                'CachingIterator',
215                                'CallbackFilterIterator',
216                                'ClosedGeneratorException',
217                                'Closure',
218                                'Collator',
219                                'CompileError',
220                                'DOMAttr',
221                                'DOMCdataSection',
222                                'DOMCharacterData',
223                                'DOMComment',
224                                'DOMConfiguration',
225                                'DOMDocument',
226                                'DOMDocumentFragment',
227                                'DOMDocumentType',
228                                'DOMDomError',
229                                'DOMElement',
230                                'DOMEntity',
231                                'DOMEntityReference',
232                                'DOMErrorHandler',
233                                'DOMException',
234                                'DOMImplementation',
235                                'DOMImplementationList',
236                                'DOMImplementationSource',
237                                'DOMLocator',
238                                'DOMNameList',
239                                'DOMNameSpaceNode',
240                                'DOMNamedNodeMap',
241                                'DOMNode',
242                                'DOMNodeList',
243                                'DOMNotation',
244                                'DOMProcessingInstruction',
245                                'DOMStringExtend',
246                                'DOMStringList',
247                                'DOMText',
248                                'DOMTypeinfo',
249                                'DOMUserDataHandler',
250                                'DOMXPath',
251                                'DateInterval',
252                                'DatePeriod',
253                                'DateTime',
254                                'DateTimeImmutable',
255                                'DateTimeZone',
256                                'Directory',
257                                'DirectoryIterator',
258                                'DivisionByZeroError',
259                                'DomainException',
260                                'EmptyIterator',
261                                'Error',
262                                'ErrorException',
263                                'Exception',
264                                'FFI',
265                                'FFI\\CData',
266                                'FFI\\CType',
267                                'FFI\\Exception',
268                                'FFI\\ParserException',
269                                'FilesystemIterator',
270                                'FilterIterator',
271                                'GMP',
272                                'Generator',
273                                'GlobIterator',
274                                'HashContext',
275                                'InfiniteIterator',
276                                'IntlBreakIterator',
277                                'IntlCalendar',
278                                'IntlChar',
279                                'IntlCodePointBreakIterator',
280                                'IntlDateFormatter',
281                                'IntlException',
282                                'IntlGregorianCalendar',
283                                'IntlIterator',
284                                'IntlPartsIterator',
285                                'IntlRuleBasedBreakIterator',
286                                'IntlTimeZone',
287                                'InvalidArgumentException',
288                                'IteratorIterator',
289                                'JsonException',
290                                'LengthException',
291                                'LibXMLError',
292                                'LimitIterator',
293                                'Locale',
294                                'LogicException',
295                                'MessageFormatter',
296                                'MultipleIterator',
297                                'NoRewindIterator',
298                                'Normalizer',
299                                'NumberFormatter',
300                                'OutOfBoundsException',
301                                'OutOfRangeException',
302                                'OverflowException',
303                                'PDO',
304                                'PDOException',
305                                'PDORow',
306                                'PDOStatement',
307                                'ParentIterator',
308                                'ParseError',
309                                'Phar',
310                                'PharData',
311                                'PharException',
312                                'PharFileInfo',
313                                'RangeException',
314                                'RecursiveArrayIterator',
315                                'RecursiveCachingIterator',
316                                'RecursiveCallbackFilterIterator',
317                                'RecursiveDirectoryIterator',
318                                'RecursiveFilterIterator',
319                                'RecursiveIteratorIterator',
320                                'RecursiveRegexIterator',
321                                'RecursiveTreeIterator',
322                                'Reflection',
323                                'ReflectionClass',
324                                'ReflectionClassConstant',
325                                'ReflectionException',
326                                'ReflectionExtension',
327                                'ReflectionFunction',
328                                'ReflectionFunctionAbstract',
329                                'ReflectionGenerator',
330                                'ReflectionMethod',
331                                'ReflectionNamedType',
332                                'ReflectionObject',
333                                'ReflectionParameter',
334                                'ReflectionProperty',
335                                'ReflectionReference',
336                                'ReflectionType',
337                                'ReflectionZendExtension',
338                                'RegexIterator',
339                                'ResourceBundle',
340                                'RuntimeException',
341                                'SQLite3',
342                                'SQLite3Result',
343                                'SQLite3Stmt',
344                                'SessionHandler',
345                                'SimpleXMLElement',
346                                'SimpleXMLIterator',
347                                'SoapClient',
348                                'SoapFault',
349                                'SoapHeader',
350                                'SoapParam',
351                                'SoapServer',
352                                'SoapVar',
353                                'SodiumException',
354                                'SplDoublyLinkedList',
355                                'SplFileInfo',
356                                'SplFileObject',
357                                'SplFixedArray',
358                                'SplHeap',
359                                'SplMaxHeap',
360                                'SplMinHeap',
361                                'SplObjectStorage',
362                                'SplPriorityQueue',
363                                'SplQueue',
364                                'SplStack',
365                                'SplTempFileObject',
366                                'Spoofchecker',
367                                'Transliterator',
368                                'TypeError',
369                                'UConverter',
370                                'UnderflowException',
371                                'UnexpectedValueException',
372                                'WeakReference',
373                                'XMLReader',
374                                'XMLWriter',
375                                'XSLTProcessor',
376                                'ZipArchive',
377                                '__PHP_Incomplete_Class',
378                                'finfo',
379                                'mysqli',
380                                'mysqli_driver',
381                                'mysqli_result',
382                                'mysqli_sql_exception',
383                                'mysqli_stmt',
384                                'mysqli_warning',
385                                'php_user_filter',
386                                'stdClass',
387                                'tidy',
388                                'tidyNode',
389                            ],
390                        'interfaces' =>
391                            [
392                                'ArrayAccess',
393                                'Countable',
394                                'DateTimeInterface',
395                                'Iterator',
396                                'IteratorAggregate',
397                                'JsonSerializable',
398                                'OuterIterator',
399                                'RecursiveIterator',
400                                'Reflector',
401                                'SeekableIterator',
402                                'Serializable',
403                                'SessionHandlerInterface',
404                                'SessionIdInterface',
405                                'SessionUpdateTimestampHandlerInterface',
406                                'SplObserver',
407                                'SplSubject',
408                                'Throwable',
409                                'Traversable',
410                            ],
411                        'traits' =>
412                            [
413                            ],
414                    ],
415                '8.1' =>
416                    [
417                        'classes' =>
418                            [
419                                'AddressInfo',
420                                'Attribute',
421                                'CURLStringFile',
422                                'CurlHandle',
423                                'CurlMultiHandle',
424                                'CurlShareHandle',
425                                'DeflateContext',
426                                'FTP\\Connection',
427                                'Fiber',
428                                'FiberError',
429                                'GdFont',
430                                'GdImage',
431                                'InflateContext',
432                                'InternalIterator',
433                                'IntlDatePatternGenerator',
434                                'LDAP\\Connection',
435                                'LDAP\\Result',
436                                'LDAP\\ResultEntry',
437                                'OpenSSLAsymmetricKey',
438                                'OpenSSLCertificate',
439                                'OpenSSLCertificateSigningRequest',
440                                'PSpell\\Config',
441                                'PSpell\\Dictionary',
442                                'PgSql\\Connection',
443                                'PgSql\\Lob',
444                                'PgSql\\Result',
445                                'PhpToken',
446                                'ReflectionAttribute',
447                                'ReflectionEnum',
448                                'ReflectionEnumBackedCase',
449                                'ReflectionEnumUnitCase',
450                                'ReflectionFiber',
451                                'ReflectionIntersectionType',
452                                'ReflectionUnionType',
453                                'ReturnTypeWillChange',
454                                'Shmop',
455                                'Socket',
456                                'SysvMessageQueue',
457                                'SysvSemaphore',
458                                'SysvSharedMemory',
459                                'UnhandledMatchError',
460                                'ValueError',
461                                'WeakMap',
462                                'XMLParser',
463                            ],
464                        'interfaces' =>
465                            [
466                                'BackedEnum',
467                                'DOMChildNode',
468                                'DOMParentNode',
469                                'Stringable',
470                                'UnitEnum',
471                            ],
472                        'traits' =>
473                            [
474                            ],
475                    ],
476                '8.2' =>
477                    [
478                        'classes' =>
479                            [
480                                'AddressInfo',
481                                'AllowDynamicProperties',
482                                'Attribute',
483                                'CURLStringFile',
484                                'CurlHandle',
485                                'CurlMultiHandle',
486                                'CurlShareHandle',
487                                'DeflateContext',
488                                'FTP\\Connection',
489                                'Fiber',
490                                'FiberError',
491                                'GdFont',
492                                'GdImage',
493                                'InflateContext',
494                                'InternalIterator',
495                                'IntlDatePatternGenerator',
496                                'LDAP\\Connection',
497                                'LDAP\\Result',
498                                'LDAP\\ResultEntry',
499                                'OpenSSLAsymmetricKey',
500                                'OpenSSLCertificate',
501                                'OpenSSLCertificateSigningRequest',
502                                'PSpell\\Config',
503                                'PSpell\\Dictionary',
504                                'PgSql\\Connection',
505                                'PgSql\\Lob',
506                                'PgSql\\Result',
507                                'PhpToken',
508                                'Random\\BrokenRandomEngineError',
509                                'Random\\Engine\\Mt19937',
510                                'Random\\Engine\\PcgOneseq128XslRr64',
511                                'Random\\Engine\\Secure',
512                                'Random\\Engine\\Xoshiro256StarStar',
513                                'Random\\RandomError',
514                                'Random\\RandomException',
515                                'Random\\Randomizer',
516                                'ReflectionAttribute',
517                                'ReflectionEnum',
518                                'ReflectionEnumBackedCase',
519                                'ReflectionEnumUnitCase',
520                                'ReflectionFiber',
521                                'ReflectionIntersectionType',
522                                'ReflectionUnionType',
523                                'ReturnTypeWillChange',
524                                'SensitiveParameter',
525                                'SensitiveParameterValue',
526                                'Shmop',
527                                'Socket',
528                                'SysvMessageQueue',
529                                'SysvSemaphore',
530                                'SysvSharedMemory',
531                                'UnhandledMatchError',
532                                'ValueError',
533                                'WeakMap',
534                                'XMLParser',
535                            ],
536                        'interfaces' =>
537                            [
538                                'BackedEnum',
539                                'DOMChildNode',
540                                'DOMParentNode',
541                                'Random\\CryptoSafeEngine',
542                                'Random\\Engine',
543                                'Stringable',
544                                'UnitEnum',
545                            ],
546                        'traits' =>
547                            [
548                            ],
549                    ],
550                '8.3' =>
551                    [
552                        'classes' =>
553                            [
554                                'AddressInfo',
555                                'AllowDynamicProperties',
556                                'Attribute',
557                                'CURLStringFile',
558                                'CurlHandle',
559                                'CurlMultiHandle',
560                                'CurlShareHandle',
561                                'DateError',
562                                'DateException',
563                                'DateInvalidOperationException',
564                                'DateInvalidTimeZoneException',
565                                'DateMalformedIntervalStringException',
566                                'DateMalformedPeriodStringException',
567                                'DateMalformedStringException',
568                                'DateObjectError',
569                                'DateRangeError',
570                                'DeflateContext',
571                                'FTP\\Connection',
572                                'Fiber',
573                                'FiberError',
574                                'GdFont',
575                                'GdImage',
576                                'InflateContext',
577                                'InternalIterator',
578                                'IntlDatePatternGenerator',
579                                'LDAP\\Connection',
580                                'LDAP\\Result',
581                                'LDAP\\ResultEntry',
582                                'OpenSSLAsymmetricKey',
583                                'OpenSSLCertificate',
584                                'OpenSSLCertificateSigningRequest',
585                                'Override',
586                                'PSpell\\Config',
587                                'PSpell\\Dictionary',
588                                'PgSql\\Connection',
589                                'PgSql\\Lob',
590                                'PgSql\\Result',
591                                'PhpToken',
592                                'Random\\BrokenRandomEngineError',
593                                'Random\\Engine\\Mt19937',
594                                'Random\\Engine\\PcgOneseq128XslRr64',
595                                'Random\\Engine\\Secure',
596                                'Random\\Engine\\Xoshiro256StarStar',
597                                'Random\\IntervalBoundary',
598                                'Random\\RandomError',
599                                'Random\\RandomException',
600                                'Random\\Randomizer',
601                                'ReflectionAttribute',
602                                'ReflectionEnum',
603                                'ReflectionEnumBackedCase',
604                                'ReflectionEnumUnitCase',
605                                'ReflectionFiber',
606                                'ReflectionIntersectionType',
607                                'ReflectionUnionType',
608                                'ReturnTypeWillChange',
609                                'SQLite3Exception',
610                                'SensitiveParameter',
611                                'SensitiveParameterValue',
612                                'Shmop',
613                                'Socket',
614                                'SysvMessageQueue',
615                                'SysvSemaphore',
616                                'SysvSharedMemory',
617                                'UnhandledMatchError',
618                                'ValueError',
619                                'WeakMap',
620                                'XMLParser',
621                            ],
622                        'interfaces' =>
623                            [
624                                'BackedEnum',
625                                'DOMChildNode',
626                                'DOMParentNode',
627                                'Random\\CryptoSafeEngine',
628                                'Random\\Engine',
629                                'Stringable',
630                                'UnitEnum',
631                            ],
632                        'traits' =>
633                            [
634                            ],
635                    ],
636                '8.4' =>
637                    [
638                        'classes' =>
639                            [
640                                'DOM\\Document',
641                                'DOM\\HTMLDocument',
642                                'DOM\\XMLDocument',
643                                'dom\\attr',
644                                'dom\\cdatasection',
645                                'dom\\characterdata',
646                                'dom\\comment',
647                                'dom\\documentfragment',
648                                'dom\\documenttype',
649                                'dom\\domexception',
650                                'dom\\element',
651                                'dom\\entity',
652                                'dom\\entityreference',
653                                'dom\\namednodemap',
654                                'dom\\namespacenode',
655                                'dom\\node',
656                                'dom\\nodelist',
657                                'dom\\notation',
658                                'dom\\processinginstruction',
659                                'dom\\text',
660                                'dom\\xpath',
661                            ],
662                        'interfaces' =>
663                            [
664                                'dom\\childnode',
665                                'dom\\parentnode',
666                            ],
667                        'traits' =>
668                            [
669                            ],
670                    ],
671                ];
672
673        $flatArray = array();
674        array_walk_recursive(
675            $builtins,
676            function ($array) use (&$flatArray) {
677                if (is_array($array)) {
678                    $flatArray = array_merge($flatArray, array_values($array));
679                } else {
680                    $flatArray[] = $array;
681                }
682            }
683        );
684        return $flatArray;
685    }
686}