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