Subiendo proyecto completo sin restricciones de git ignore

This commit is contained in:
Jose Sanchez
2023-08-17 11:44:02 -04:00
parent a0d4f5ba3b
commit 20f1c60600
19921 changed files with 2509159 additions and 45 deletions

View File

@@ -0,0 +1,19 @@
Copyright (C) 2022 Composer
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,66 @@
composer/class-map-generator
============================
Utilities to generate class maps and scan PHP code.
[![Continuous Integration](https://github.com/composer/class-map-generator/workflows/Continuous%20Integration/badge.svg?branch=main)](https://github.com/composer/class-map-generator/actions)
Installation
------------
Install the latest version with:
```bash
$ composer require composer/class-map-generator
```
Requirements
------------
* PHP 7.2 is required.
Basic usage
-----------
If all you want is to scan a directory and extract a classmap with all
classes/interfaces/traits/enums mapped to their paths, you can simply use:
```php
use Composer\ClassMapGenerator\ClassMapGenerator;
$map = ClassMapGenerator::createMap('path/to/scan');
foreach ($map as $symbol => $path) {
// do your thing
}
```
For more advanced usage, you can instantiate a generator object and call scanPaths one or more time
then call getClassMap to get a ClassMap object containing the resulting map + eventual warnings.
```php
use Composer\ClassMapGenerator\ClassMapGenerator;
$generator = new ClassMapGenerator;
$generator->scanPaths('path/to/scan');
$generator->scanPaths('path/to/scan2');
$classMap = $generator->getClassMap();
$classMap->sort(); // optionally sort classes alphabetically
foreach ($classMap->getMap() as $symbol => $path) {
// do your thing
}
foreach ($classMap->getAmbiguousClasses() as $symbol => $paths) {
// warn user about ambiguous class resolution
}
```
License
-------
composer/class-map-generator is licensed under the MIT License, see the LICENSE file for details.

View File

@@ -0,0 +1,48 @@
{
"name": "composer/class-map-generator",
"description": "Utilities to scan PHP code and generate class maps.",
"type": "library",
"license": "MIT",
"keywords": [
"classmap"
],
"authors": [
{
"name": "Jordi Boggiano",
"email": "j.boggiano@seld.be",
"homepage": "https://seld.be"
}
],
"require": {
"php": "^7.2 || ^8.0",
"symfony/finder": "^4.4 || ^5.3 || ^6 || ^7",
"composer/pcre": "^2.1 || ^3.1"
},
"require-dev": {
"symfony/phpunit-bridge": "^5",
"phpstan/phpstan": "^1.6",
"phpstan/phpstan-deprecation-rules": "^1",
"phpstan/phpstan-strict-rules": "^1.1",
"phpstan/phpstan-phpunit": "^1",
"symfony/filesystem": "^5.4 || ^6"
},
"autoload": {
"psr-4": {
"Composer\\ClassMapGenerator\\": "src"
}
},
"autoload-dev": {
"psr-4": {
"Composer\\ClassMapGenerator\\": "tests"
}
},
"extra": {
"branch-alias": {
"dev-main": "1.x-dev"
}
},
"scripts": {
"test": "SYMFONY_PHPUNIT_REMOVE_RETURN_TYPEHINT=1 vendor/bin/simple-phpunit",
"phpstan": "phpstan analyse"
}
}

View File

@@ -0,0 +1,131 @@
<?php declare(strict_types=1);
/*
* This file is part of Composer.
*
* (c) Nils Adermann <naderman@naderman.de>
* Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Composer\ClassMapGenerator;
/**
* @author Jordi Boggiano <j.boggiano@seld.be>
*/
class ClassMap implements \Countable
{
/**
* @var array<class-string, non-empty-string>
*/
public $map = [];
/**
* @var array<class-string, array<non-empty-string>>
*/
private $ambiguousClasses = [];
/**
* @var string[]
*/
private $psrViolations = [];
/**
* Returns the class map, which is a list of paths indexed by class name
*
* @return array<class-string, non-empty-string>
*/
public function getMap(): array
{
return $this->map;
}
/**
* Returns warning strings containing details about PSR-0/4 violations that were detected
*
* Violations are for ex a class which is in the wrong file/directory and thus should not be
* found using psr-0/psr-4 autoloading but was found by the ClassMapGenerator as it scans all files.
*
* This is only happening when scanning paths using psr-0/psr-4 autoload type. Classmap type
* always accepts every class as it finds it.
*
* @return string[]
*/
public function getPsrViolations(): array
{
return $this->psrViolations;
}
/**
* A map of class names to their list of ambiguous paths
*
* This occurs when the same class can be found in several files
*
* To get the path the class is being mapped to, call getClassPath
*
* @return array<class-string, array<non-empty-string>>
*/
public function getAmbiguousClasses(): array
{
return $this->ambiguousClasses;
}
/**
* Sorts the class map alphabetically by class names
*/
public function sort(): void
{
ksort($this->map);
}
/**
* @param class-string $className
* @param non-empty-string $path
*/
public function addClass(string $className, string $path): void
{
$this->map[$className] = $path;
}
/**
* @param class-string $className
* @return non-empty-string
*/
public function getClassPath(string $className): string
{
if (!isset($this->map[$className])) {
throw new \OutOfBoundsException('Class '.$className.' is not present in the map');
}
return $this->map[$className];
}
/**
* @param class-string $className
*/
public function hasClass(string $className): bool
{
return isset($this->map[$className]);
}
public function addPsrViolation(string $warning): void
{
$this->psrViolations[] = $warning;
}
/**
* @param class-string $className
* @param non-empty-string $path
*/
public function addAmbiguousClass(string $className, string $path): void
{
$this->ambiguousClasses[$className][] = $path;
}
public function count(): int
{
return \count($this->map);
}
}

View File

@@ -0,0 +1,334 @@
<?php declare(strict_types=1);
/*
* This file is part of Composer.
*
* (c) Nils Adermann <naderman@naderman.de>
* Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/*
* This file was initially based on a version from the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*/
namespace Composer\ClassMapGenerator;
use Composer\Pcre\Preg;
use Symfony\Component\Finder\Finder;
use Composer\IO\IOInterface;
/**
* ClassMapGenerator
*
* @author Gyula Sallai <salla016@gmail.com>
* @author Jordi Boggiano <j.boggiano@seld.be>
*/
class ClassMapGenerator
{
/**
* @var list<string>
*/
private $extensions;
/**
* @var FileList|null
*/
private $scannedFiles = null;
/**
* @var ClassMap
*/
private $classMap;
/**
* @param list<string> $extensions File extensions to scan for classes in the given paths
*/
public function __construct(array $extensions = ['php', 'inc'])
{
$this->extensions = $extensions;
$this->classMap = new ClassMap;
}
/**
* When calling scanPaths repeatedly with paths that may overlap, calling this will ensure that the same class is never scanned twice
*
* You can provide your own FileList instance or use the default one if you pass no argument
*
* @return $this
*/
public function avoidDuplicateScans(FileList $scannedFiles = null): self
{
$this->scannedFiles = $scannedFiles ?? new FileList;
return $this;
}
/**
* Iterate over all files in the given directory searching for classes
*
* @param string|\Traversable<\SplFileInfo>|array<\SplFileInfo> $path The path to search in or an array/traversable of SplFileInfo (e.g. symfony/finder instance)
* @return array<class-string, non-empty-string> A class map array
*
* @throws \RuntimeException When the path is neither an existing file nor directory
*/
public static function createMap($path): array
{
$generator = new self();
$generator->scanPaths($path);
return $generator->getClassMap()->getMap();
}
public function getClassMap(): ClassMap
{
return $this->classMap;
}
/**
* Iterate over all files in the given directory searching for classes
*
* @param string|\Traversable<\SplFileInfo>|array<\SplFileInfo> $path The path to search in or an array/traversable of SplFileInfo (e.g. symfony/finder instance)
* @param non-empty-string|null $excluded Regex that matches file paths to be excluded from the classmap
* @param 'classmap'|'psr-0'|'psr-4' $autoloadType Optional autoload standard to use mapping rules with the namespace instead of purely doing a classmap
* @param string|null $namespace Optional namespace prefix to filter by, only for psr-0/psr-4 autoloading
*
* @throws \RuntimeException When the path is neither an existing file nor directory
*/
public function scanPaths($path, string $excluded = null, string $autoloadType = 'classmap', ?string $namespace = null): void
{
if (!in_array($autoloadType, ['psr-0', 'psr-4', 'classmap'], true)) {
throw new \InvalidArgumentException('$autoloadType must be one of: "psr-0", "psr-4" or "classmap"');
}
if ('classmap' !== $autoloadType) {
if (!is_string($path)) {
throw new \InvalidArgumentException('$path must be a string when specifying a psr-0 or psr-4 autoload type');
}
if (!is_string($namespace)) {
throw new \InvalidArgumentException('$namespace must be given (even if it is an empty string if you do not want to filter) when specifying a psr-0 or psr-4 autoload type');
}
$basePath = $path;
}
if (is_string($path)) {
if (is_file($path)) {
$path = [new \SplFileInfo($path)];
} elseif (is_dir($path) || strpos($path, '*') !== false) {
$path = Finder::create()
->files()
->followLinks()
->name('/\.(?:'.implode('|', array_map('preg_quote', $this->extensions)).')$/')
->in($path);
} else {
throw new \RuntimeException(
'Could not scan for classes inside "'.$path.'" which does not appear to be a file nor a folder'
);
}
}
$cwd = realpath(self::getCwd());
foreach ($path as $file) {
$filePath = $file->getPathname();
if (!in_array(pathinfo($filePath, PATHINFO_EXTENSION), $this->extensions, true)) {
continue;
}
if (!self::isAbsolutePath($filePath)) {
$filePath = $cwd . '/' . $filePath;
$filePath = self::normalizePath($filePath);
} else {
$filePath = Preg::replace('{[\\\\/]{2,}}', '/', $filePath);
}
if ('' === $filePath) {
throw new \LogicException('Got an empty $filePath for '.$file->getPathname());
}
$realPath = realpath($filePath);
// fallback just in case but this really should not happen
if (false === $realPath) {
throw new \RuntimeException('realpath of '.$filePath.' failed to resolve, got false');
}
// if a list of scanned files is given, avoid scanning twice the same file to save cycles and avoid generating warnings
// in case a PSR-0/4 declaration follows another more specific one, or a classmap declaration, which covered this file already
if ($this->scannedFiles !== null && $this->scannedFiles->contains($realPath)) {
continue;
}
// check the realpath of the file against the excluded paths as the path might be a symlink and the excluded path is realpath'd so symlink are resolved
if (null !== $excluded && Preg::isMatch($excluded, strtr($realPath, '\\', '/'))) {
continue;
}
// check non-realpath of file for directories symlink in project dir
if (null !== $excluded && Preg::isMatch($excluded, strtr($filePath, '\\', '/'))) {
continue;
}
$classes = PhpFileParser::findClasses($filePath);
if ('classmap' !== $autoloadType && isset($namespace)) {
$classes = $this->filterByNamespace($classes, $filePath, $namespace, $autoloadType, $basePath);
// if no valid class was found in the file then we do not mark it as scanned as it might still be matched by another rule later
if (\count($classes) > 0 && $this->scannedFiles !== null) {
$this->scannedFiles->add($realPath);
}
} elseif ($this->scannedFiles !== null) {
// classmap autoload rules always collect all classes so for these we definitely do not want to scan again
$this->scannedFiles->add($realPath);
}
foreach ($classes as $class) {
if (!$this->classMap->hasClass($class)) {
$this->classMap->addClass($class, $filePath);
} elseif ($filePath !== $this->classMap->getClassPath($class) && !Preg::isMatch('{/(test|fixture|example|stub)s?/}i', strtr($this->classMap->getClassPath($class).' '.$filePath, '\\', '/'))) {
$this->classMap->addAmbiguousClass($class, $filePath);
}
}
}
}
/**
* Remove classes which could not have been loaded by namespace autoloaders
*
* @param array<int, class-string> $classes found classes in given file
* @param string $filePath current file
* @param string $baseNamespace prefix of given autoload mapping
* @param 'psr-0'|'psr-4' $namespaceType
* @param string $basePath root directory of given autoload mapping
* @return array<int, class-string> valid classes
*/
private function filterByNamespace(array $classes, string $filePath, string $baseNamespace, string $namespaceType, string $basePath): array
{
$validClasses = [];
$rejectedClasses = [];
$realSubPath = substr($filePath, strlen($basePath) + 1);
$dotPosition = strrpos($realSubPath, '.');
$realSubPath = substr($realSubPath, 0, $dotPosition === false ? PHP_INT_MAX : $dotPosition);
foreach ($classes as $class) {
// silently skip if ns doesn't have common root
if ('' !== $baseNamespace && 0 !== strpos($class, $baseNamespace)) {
continue;
}
// transform class name to file path and validate
if ('psr-0' === $namespaceType) {
$namespaceLength = strrpos($class, '\\');
if (false !== $namespaceLength) {
$namespace = substr($class, 0, $namespaceLength + 1);
$className = substr($class, $namespaceLength + 1);
$subPath = str_replace('\\', DIRECTORY_SEPARATOR, $namespace)
. str_replace('_', DIRECTORY_SEPARATOR, $className);
} else {
$subPath = str_replace('_', DIRECTORY_SEPARATOR, $class);
}
} elseif ('psr-4' === $namespaceType) {
$subNamespace = ('' !== $baseNamespace) ? substr($class, strlen($baseNamespace)) : $class;
$subPath = str_replace('\\', DIRECTORY_SEPARATOR, $subNamespace);
} else {
throw new \InvalidArgumentException('$namespaceType must be "psr-0" or "psr-4"');
}
if ($subPath === $realSubPath) {
$validClasses[] = $class;
} else {
$rejectedClasses[] = $class;
}
}
// warn only if no valid classes, else silently skip invalid
if (\count($validClasses) === 0) {
foreach ($rejectedClasses as $class) {
$this->classMap->addPsrViolation("Class $class located in ".Preg::replace('{^'.preg_quote(self::getCwd()).'}', '.', $filePath, 1)." does not comply with $namespaceType autoloading standard. Skipping.");
}
return [];
}
return $validClasses;
}
/**
* Checks if the given path is absolute
*
* @see Composer\Util\Filesystem::isAbsolutePath
*
* @param string $path
* @return bool
*/
private static function isAbsolutePath(string $path)
{
return strpos($path, '/') === 0 || substr($path, 1, 1) === ':' || strpos($path, '\\\\') === 0;
}
/**
* Normalize a path. This replaces backslashes with slashes, removes ending
* slash and collapses redundant separators and up-level references.
*
* @see Composer\Util\Filesystem::normalizePath
*
* @param string $path Path to the file or directory
* @return string
*/
private static function normalizePath(string $path)
{
$parts = [];
$path = strtr($path, '\\', '/');
$prefix = '';
$absolute = '';
// extract windows UNC paths e.g. \\foo\bar
if (strpos($path, '//') === 0 && \strlen($path) > 2) {
$absolute = '//';
$path = substr($path, 2);
}
// extract a prefix being a protocol://, protocol:, protocol://drive: or simply drive:
if (Preg::isMatchStrictGroups('{^( [0-9a-z]{2,}+: (?: // (?: [a-z]: )? )? | [a-z]: )}ix', $path, $match)) {
$prefix = $match[1];
$path = substr($path, \strlen($prefix));
}
if (strpos($path, '/') === 0) {
$absolute = '/';
$path = substr($path, 1);
}
$up = false;
foreach (explode('/', $path) as $chunk) {
if ('..' === $chunk && (\strlen($absolute) > 0 || $up)) {
array_pop($parts);
$up = !(\count($parts) === 0 || '..' === end($parts));
} elseif ('.' !== $chunk && '' !== $chunk) {
$parts[] = $chunk;
$up = '..' !== $chunk;
}
}
// ensure c: is normalized to C:
$prefix = Preg::replaceCallback('{(?:^|://)[a-z]:$}i', function (array $m) { return strtoupper((string) $m[0]); }, $prefix);
return $prefix.$absolute.implode('/', $parts);
}
/**
* @see Composer\Util\Platform::getCwd
*/
private static function getCwd(): string
{
$cwd = getcwd();
if (false === $cwd) {
throw new \RuntimeException('Could not determine the current working directory');
}
return $cwd;
}
}

View File

@@ -0,0 +1,42 @@
<?php declare(strict_types=1);
/*
* This file is part of Composer.
*
* (c) Nils Adermann <naderman@naderman.de>
* Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Composer\ClassMapGenerator;
/**
* Contains a list of files which were scanned to generate a classmap
*
* @author Jordi Boggiano <j.boggiano@seld.be>
*/
class FileList
{
/**
* @var array<non-empty-string, true>
*/
public $files = [];
/**
* @param non-empty-string $path
*/
public function add(string $path): void
{
$this->files[$path] = true;
}
/**
* @param non-empty-string $path
*/
public function contains(string $path): bool
{
return isset($this->files[$path]);
}
}

View File

@@ -0,0 +1,247 @@
<?php declare(strict_types=1);
/*
* This file is part of Composer.
*
* (c) Nils Adermann <naderman@naderman.de>
* Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Composer\ClassMapGenerator;
use Composer\Pcre\Preg;
/**
* @author Jordi Boggiano <j.boggiano@seld.be>
* @internal
*/
class PhpFileCleaner
{
/** @var array<array{name: string, length: int, pattern: non-empty-string}> */
private static $typeConfig;
/** @var non-empty-string */
private static $restPattern;
/**
* @readonly
* @var string
*/
private $contents;
/**
* @readonly
* @var int
*/
private $len;
/**
* @readonly
* @var int
*/
private $maxMatches;
/** @var int */
private $index = 0;
/**
* @param string[] $types
*/
public static function setTypeConfig(array $types): void
{
foreach ($types as $type) {
self::$typeConfig[$type[0]] = array(
'name' => $type,
'length' => \strlen($type),
'pattern' => '{.\b(?<![\$:>])'.$type.'\s++[a-zA-Z_\x7f-\xff:][a-zA-Z0-9_\x7f-\xff:\-]*+}Ais',
);
}
self::$restPattern = '{[^?"\'</'.implode('', array_keys(self::$typeConfig)).']+}A';
}
public function __construct(string $contents, int $maxMatches)
{
$this->contents = $contents;
$this->len = \strlen($this->contents);
$this->maxMatches = $maxMatches;
}
public function clean(): string
{
$clean = '';
while ($this->index < $this->len) {
$this->skipToPhp();
$clean .= '<?';
while ($this->index < $this->len) {
$char = $this->contents[$this->index];
if ($char === '?' && $this->peek('>')) {
$clean .= '?>';
$this->index += 2;
continue 2;
}
if ($char === '"') {
$this->skipString('"');
$clean .= 'null';
continue;
}
if ($char === "'") {
$this->skipString("'");
$clean .= 'null';
continue;
}
if ($char === "<" && $this->peek('<') && $this->match('{<<<[ \t]*+([\'"]?)([a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*+)\\1(?:\r\n|\n|\r)}A', $match)) {
$this->index += \strlen($match[0]);
$this->skipHeredoc($match[2]);
$clean .= 'null';
continue;
}
if ($char === '/') {
if ($this->peek('/')) {
$this->skipToNewline();
continue;
}
if ($this->peek('*')) {
$this->skipComment();
continue;
}
}
if ($this->maxMatches === 1 && isset(self::$typeConfig[$char])) {
$type = self::$typeConfig[$char];
if (
\substr($this->contents, $this->index, $type['length']) === $type['name']
&& Preg::isMatch($type['pattern'], $this->contents, $match, 0, $this->index - 1)
) {
$clean .= $match[0];
return $clean;
}
}
$this->index += 1;
if ($this->match(self::$restPattern, $match)) {
$clean .= $char . $match[0];
$this->index += \strlen($match[0]);
} else {
$clean .= $char;
}
}
}
return $clean;
}
private function skipToPhp(): void
{
while ($this->index < $this->len) {
if ($this->contents[$this->index] === '<' && $this->peek('?')) {
$this->index += 2;
break;
}
$this->index += 1;
}
}
private function skipString(string $delimiter): void
{
$this->index += 1;
while ($this->index < $this->len) {
if ($this->contents[$this->index] === '\\' && ($this->peek('\\') || $this->peek($delimiter))) {
$this->index += 2;
continue;
}
if ($this->contents[$this->index] === $delimiter) {
$this->index += 1;
break;
}
$this->index += 1;
}
}
private function skipComment(): void
{
$this->index += 2;
while ($this->index < $this->len) {
if ($this->contents[$this->index] === '*' && $this->peek('/')) {
$this->index += 2;
break;
}
$this->index += 1;
}
}
private function skipToNewline(): void
{
while ($this->index < $this->len) {
if ($this->contents[$this->index] === "\r" || $this->contents[$this->index] === "\n") {
return;
}
$this->index += 1;
}
}
private function skipHeredoc(string $delimiter): void
{
$firstDelimiterChar = $delimiter[0];
$delimiterLength = \strlen($delimiter);
$delimiterPattern = '{'.preg_quote($delimiter).'(?![a-zA-Z0-9_\x80-\xff])}A';
while ($this->index < $this->len) {
// check if we find the delimiter after some spaces/tabs
switch ($this->contents[$this->index]) {
case "\t":
case " ":
$this->index += 1;
continue 2;
case $firstDelimiterChar:
if (
\substr($this->contents, $this->index, $delimiterLength) === $delimiter
&& $this->match($delimiterPattern)
) {
$this->index += $delimiterLength;
return;
}
break;
}
// skip the rest of the line
while ($this->index < $this->len) {
$this->skipToNewline();
// skip newlines
while ($this->index < $this->len && ($this->contents[$this->index] === "\r" || $this->contents[$this->index] === "\n")) {
$this->index += 1;
}
break;
}
}
}
private function peek(string $char): bool
{
return $this->index + 1 < $this->len && $this->contents[$this->index + 1] === $char;
}
/**
* @param non-empty-string $regex
* @param null|array<int, string> $match
*/
private function match(string $regex, array &$match = null): bool
{
return Preg::isMatch($regex, $this->contents, $match, 0, $this->index);
}
}

View File

@@ -0,0 +1,151 @@
<?php declare(strict_types=1);
/*
* This file is part of Composer.
*
* (c) Nils Adermann <naderman@naderman.de>
* Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Composer\ClassMapGenerator;
use Composer\Pcre\Preg;
/**
* @author Jordi Boggiano <j.boggiano@seld.be>
*/
class PhpFileParser
{
/**
* Extract the classes in the given file
*
* @param string $path The file to check
* @throws \RuntimeException
* @return array<int, class-string> The found classes
*/
public static function findClasses(string $path): array
{
$extraTypes = self::getExtraTypes();
// Use @ here instead of Silencer to actively suppress 'unhelpful' output
// @link https://github.com/composer/composer/pull/4886
$contents = @php_strip_whitespace($path);
if ('' === $contents) {
if (!file_exists($path)) {
$message = 'File at "%s" does not exist, check your classmap definitions';
} elseif (!self::isReadable($path)) {
$message = 'File at "%s" is not readable, check its permissions';
} elseif ('' === trim((string) file_get_contents($path))) {
// The input file was really empty and thus contains no classes
return array();
} else {
$message = 'File at "%s" could not be parsed as PHP, it may be binary or corrupted';
}
$error = error_get_last();
if (isset($error['message'])) {
$message .= PHP_EOL . 'The following message may be helpful:' . PHP_EOL . $error['message'];
}
throw new \RuntimeException(sprintf($message, $path));
}
// return early if there is no chance of matching anything in this file
Preg::matchAllStrictGroups('{\b(?:class|interface|trait'.$extraTypes.')\s}i', $contents, $matches);
if (0 === \count($matches)) {
return array();
}
$p = new PhpFileCleaner($contents, count($matches[0]));
$contents = $p->clean();
unset($p);
Preg::matchAll('{
(?:
\b(?<![\$:>])(?P<type>class|interface|trait'.$extraTypes.') \s++ (?P<name>[a-zA-Z_\x7f-\xff:][a-zA-Z0-9_\x7f-\xff:\-]*+)
| \b(?<![\$:>])(?P<ns>namespace) (?P<nsname>\s++[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+(?:\s*+\\\\\s*+[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+)*+)? \s*+ [\{;]
)
}ix', $contents, $matches);
$classes = array();
$namespace = '';
for ($i = 0, $len = count($matches['type']); $i < $len; $i++) {
if (isset($matches['ns'][$i]) && $matches['ns'][$i] !== '') {
$namespace = str_replace(array(' ', "\t", "\r", "\n"), '', (string) $matches['nsname'][$i]) . '\\';
} else {
$name = $matches['name'][$i];
assert(is_string($name));
// skip anon classes extending/implementing
if ($name === 'extends' || $name === 'implements') {
continue;
}
if ($name[0] === ':') {
// This is an XHP class, https://github.com/facebook/xhp
$name = 'xhp'.substr(str_replace(array('-', ':'), array('_', '__'), $name), 1);
} elseif (strtolower((string) $matches['type'][$i]) === 'enum') {
// something like:
// enum Foo: int { HERP = '123'; }
// The regex above captures the colon, which isn't part of
// the class name.
// or:
// enum Foo:int { HERP = '123'; }
// The regex above captures the colon and type, which isn't part of
// the class name.
$colonPos = strrpos($name, ':');
if (false !== $colonPos) {
$name = substr($name, 0, $colonPos);
}
}
$classes[] = ltrim($namespace . $name, '\\');
}
}
return $classes;
}
/**
* @return string
*/
private static function getExtraTypes(): string
{
static $extraTypes = null;
if (null === $extraTypes) {
$extraTypes = '';
if (PHP_VERSION_ID >= 80100 || (defined('HHVM_VERSION') && version_compare(HHVM_VERSION, '3.3', '>='))) {
$extraTypes .= '|enum';
}
PhpFileCleaner::setTypeConfig(array_merge(['class', 'interface', 'trait'], array_filter(explode('|', $extraTypes))));
}
return $extraTypes;
}
/**
* Cross-platform safe version of is_readable()
*
* This will also check for readability by reading the file as is_readable can not be trusted on network-mounts
* and \\wsl$ paths. See https://github.com/composer/composer/issues/8231 and https://bugs.php.net/bug.php?id=68926
*
* @see Composer\Util\Filesystem::isReadable
*
* @param string $path
* @return bool
*/
private static function isReadable(string $path)
{
if (is_readable($path)) {
return true;
}
if (is_file($path)) {
return false !== @file_get_contents($path, false, null, 0, 1);
}
// assume false otherwise
return false;
}
}