Compare commits
5 Commits
0f646bba37
...
dc91a39b4c
Author | SHA1 | Date |
---|---|---|
Florian Brinker | dc91a39b4c | |
Florian Brinker | 7f2305ec04 | |
Florian Brinker | 3982e7aa95 | |
Florian Brinker | e9c3deceef | |
Florian Brinker | 9ac0fe07dd |
|
@ -0,0 +1,4 @@
|
|||
/tmp/
|
||||
/vendor/
|
||||
*.phar
|
||||
cs-check.json
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Listen for Xdebug",
|
||||
"type": "php",
|
||||
"request": "launch",
|
||||
"port": 9000,
|
||||
"pathMappings": {
|
||||
"/app": "${workspaceFolder}/"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
#!/usr/bin/env php
|
||||
<?php
|
||||
require __DIR__.'/../vendor/autoload.php';
|
||||
|
||||
use Fbrinker\ExtensionCheck\Command\CheckCommand;
|
||||
use Laminas\ServiceManager\ServiceManager;
|
||||
use Symfony\Component\Console\Application;
|
||||
use Symfony\Component\Console\Input\ArgvInput;
|
||||
|
||||
$serviceManager = new ServiceManager(require("config/serviceManager.php"));
|
||||
$application = new Application();
|
||||
|
||||
$command = $serviceManager->get(CheckCommand::class);
|
||||
$application->add($command);
|
||||
|
||||
// Prepend the command as default command to accept arguments and options if necessary
|
||||
// Workaround, since the Symfony $application->setDefaultCommand() can't accept arguments or options
|
||||
if (!isset($argv[1]) || $argv[1] !== CheckCommand::getDefaultName()) {
|
||||
$argv = array_merge([$argv[0], CheckCommand::getDefaultName()], array_slice($argv, 1));
|
||||
}
|
||||
$application->run(new ArgvInput($argv));
|
|
@ -0,0 +1,47 @@
|
|||
{
|
||||
"name": "fbrinker/extension-check",
|
||||
"description": "Checks your code for usages of loaded PHP Extensions",
|
||||
"keywords": [
|
||||
"extensions",
|
||||
"php-extensions",
|
||||
"unused",
|
||||
"php-parser"
|
||||
],
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Florian Brinker",
|
||||
"email": "mail+extension-check@f-brinker.de"
|
||||
}
|
||||
],
|
||||
"bin": [
|
||||
"bin/extension-check"
|
||||
],
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Fbrinker\\ExtensionCheck\\": "src"
|
||||
}
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"Fbrinker\\ExtensionCheck\\Tests\\Unit\\": "tests/Unit"
|
||||
}
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.2",
|
||||
"laminas/laminas-servicemanager": "^3.5",
|
||||
"nikic/php-parser": "^4.10",
|
||||
"symfony/console": "^5.2",
|
||||
"symfony/finder": "^5.2"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpstan/phpstan": "^0.12.86",
|
||||
"phpunit/phpunit": "^8.5",
|
||||
"roave/security-advisories": "dev-latest",
|
||||
"squizlabs/php_codesniffer": "^3.6"
|
||||
},
|
||||
"config": {
|
||||
"preferred-install": "dist",
|
||||
"sort-packages": true
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,21 @@
|
|||
<?php
|
||||
|
||||
use Fbrinker\ExtensionCheck\Command\CheckCommand;
|
||||
use Fbrinker\ExtensionCheck\Command\CheckCommandFactory;
|
||||
use Fbrinker\ExtensionCheck\Extension\ExtensionCheck;
|
||||
use Fbrinker\ExtensionCheck\Extension\ExtensionCheckFactory;
|
||||
use Fbrinker\ExtensionCheck\Extension\ExtensionDetails;
|
||||
use Fbrinker\ExtensionCheck\Output\SymfonyStyleFactory;
|
||||
use Fbrinker\ExtensionCheck\Parser\FileParser;
|
||||
use Fbrinker\ExtensionCheck\Parser\FileParserFactory;
|
||||
use Laminas\ServiceManager\Factory\InvokableFactory;
|
||||
|
||||
return [
|
||||
'factories' => [
|
||||
CheckCommand::class => CheckCommandFactory::class,
|
||||
ExtensionCheck::class => ExtensionCheckFactory::class,
|
||||
ExtensionDetails::class => InvokableFactory::class,
|
||||
FileParser::class => FileParserFactory::class,
|
||||
SymfonyStyleFactory::class => InvokableFactory::class,
|
||||
],
|
||||
];
|
|
@ -0,0 +1,29 @@
|
|||
version: "3.7"
|
||||
|
||||
services:
|
||||
php7.2:
|
||||
build: docker/php7.2
|
||||
container_name: extension-check-7.2
|
||||
volumes:
|
||||
- .:/app:rw
|
||||
tty: true
|
||||
extra_hosts:
|
||||
- "host.docker.internal:host-gateway"
|
||||
|
||||
php7.4:
|
||||
build: docker/php7.4
|
||||
container_name: extension-check-7.4
|
||||
volumes:
|
||||
- .:/app:rw
|
||||
tty: true
|
||||
extra_hosts:
|
||||
- "host.docker.internal:host-gateway"
|
||||
|
||||
php8.0:
|
||||
build: docker/php8.0
|
||||
container_name: extension-check-8.0
|
||||
volumes:
|
||||
- .:/app:rw
|
||||
tty: true
|
||||
extra_hosts:
|
||||
- "host.docker.internal:host-gateway"
|
|
@ -0,0 +1,18 @@
|
|||
FROM php:7.2-alpine
|
||||
|
||||
COPY --from=mlocati/php-extension-installer /usr/bin/install-php-extensions /usr/bin/
|
||||
RUN install-php-extensions xdebug
|
||||
|
||||
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
|
||||
ENV COMPOSER_ALLOW_SUPERUSER 1
|
||||
|
||||
RUN echo "xdebug.start_with_request=yes" >> /usr/local/etc/php/conf.d/xdebug.ini \
|
||||
&& echo "xdebug.mode=debug" >> /usr/local/etc/php/conf.d/xdebug.ini \
|
||||
&& echo "xdebug.log=/tmp/xdebug.log" >> /usr/local/etc/php/conf.d/xdebug.ini \
|
||||
&& echo "xdebug.discover_client_host=1" >> /usr/local/etc/php/conf.d/xdebug.ini \
|
||||
&& echo "xdebug.client_port=9000" >> /usr/local/etc/php/conf.d/xdebug.ini \
|
||||
&& echo "xdebug.client_host=host.docker.internal" >> /usr/local/etc/php/conf.d/xdebug.ini
|
||||
|
||||
WORKDIR /docker
|
||||
# Workaround to keep container running
|
||||
CMD ["tail", "-f", "/dev/null"]
|
|
@ -0,0 +1,18 @@
|
|||
FROM php:7.4-alpine
|
||||
|
||||
COPY --from=mlocati/php-extension-installer /usr/bin/install-php-extensions /usr/bin/
|
||||
RUN install-php-extensions xdebug
|
||||
|
||||
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
|
||||
ENV COMPOSER_ALLOW_SUPERUSER 1
|
||||
|
||||
RUN echo "xdebug.start_with_request=yes" >> /usr/local/etc/php/conf.d/xdebug.ini \
|
||||
&& echo "xdebug.mode=debug" >> /usr/local/etc/php/conf.d/xdebug.ini \
|
||||
&& echo "xdebug.log=/tmp/xdebug.log" >> /usr/local/etc/php/conf.d/xdebug.ini \
|
||||
&& echo "xdebug.discover_client_host=1" >> /usr/local/etc/php/conf.d/xdebug.ini \
|
||||
&& echo "xdebug.client_port=9000" >> /usr/local/etc/php/conf.d/xdebug.ini \
|
||||
&& echo "xdebug.client_host=host.docker.internal" >> /usr/local/etc/php/conf.d/xdebug.ini
|
||||
|
||||
WORKDIR /docker
|
||||
# Workaround to keep container running
|
||||
CMD ["tail", "-f", "/dev/null"]
|
|
@ -0,0 +1,18 @@
|
|||
FROM php:8.0-alpine
|
||||
|
||||
COPY --from=mlocati/php-extension-installer /usr/bin/install-php-extensions /usr/bin/
|
||||
RUN install-php-extensions xdebug
|
||||
|
||||
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
|
||||
ENV COMPOSER_ALLOW_SUPERUSER 1
|
||||
|
||||
RUN echo "xdebug.start_with_request=yes" >> /usr/local/etc/php/conf.d/xdebug.ini \
|
||||
&& echo "xdebug.mode=debug" >> /usr/local/etc/php/conf.d/xdebug.ini \
|
||||
&& echo "xdebug.log=/tmp/xdebug.log" >> /usr/local/etc/php/conf.d/xdebug.ini \
|
||||
&& echo "xdebug.discover_client_host=1" >> /usr/local/etc/php/conf.d/xdebug.ini \
|
||||
&& echo "xdebug.client_port=9000" >> /usr/local/etc/php/conf.d/xdebug.ini \
|
||||
&& echo "xdebug.client_host=host.docker.internal" >> /usr/local/etc/php/conf.d/xdebug.ini
|
||||
|
||||
WORKDIR /docker
|
||||
# Workaround to keep container running
|
||||
CMD ["tail", "-f", "/dev/null"]
|
|
@ -0,0 +1,24 @@
|
|||
<?xml version="1.0"?>
|
||||
<ruleset name="Skeleton coding standard">
|
||||
<description>Skeleton coding standard</description>
|
||||
|
||||
<arg value="p"/>
|
||||
<arg name="colors"/>
|
||||
<arg name="cache" value="cs-check.json"/>
|
||||
<arg name="parallel" value="8" />
|
||||
<arg name="ignore" value="tests/assets"/>
|
||||
|
||||
<rule ref="PSR12">
|
||||
<exclude name="Generic.Files.LineLength"/>
|
||||
</rule>
|
||||
<rule ref="Generic.Arrays.DisallowLongArraySyntax"/>
|
||||
<rule ref="Squiz.WhiteSpace.SuperfluousWhitespace">
|
||||
<properties>
|
||||
<property name="ignoreBlankLines" value="false"/>
|
||||
</properties>
|
||||
</rule>
|
||||
|
||||
<file>src</file>
|
||||
<file>tests</file>
|
||||
<file>config</file>
|
||||
</ruleset>
|
|
@ -0,0 +1,9 @@
|
|||
parameters:
|
||||
level: max
|
||||
inferPrivatePropertyTypeFromConstructor: true
|
||||
paths:
|
||||
- src/
|
||||
- tests/
|
||||
excludes_analyse:
|
||||
- %currentWorkingDirectory%/src/TEST
|
||||
tmpDir: tmp/phpstan
|
|
@ -0,0 +1,169 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Fbrinker\ExtensionCheck\Command;
|
||||
|
||||
use Closure;
|
||||
use Fbrinker\ExtensionCheck\Extension\ExtensionCheck;
|
||||
use Fbrinker\ExtensionCheck\Output\SymfonyStyleFactory;
|
||||
use Fbrinker\ExtensionCheck\Parser\FileParser;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Helper\ProgressBar;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputDefinition;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
class CheckCommand extends Command
|
||||
{
|
||||
private const ARG_DIRECTORY = 'directory';
|
||||
private const OPT_EXCLUDE = 'exclude';
|
||||
|
||||
protected static $defaultName = 'check';
|
||||
private $symfonyStyleFactory;
|
||||
private $fileParser;
|
||||
private $extensionCheck;
|
||||
|
||||
public function __construct(
|
||||
SymfonyStyleFactory $symfonyStyleFactory,
|
||||
FileParser $fileParser,
|
||||
ExtensionCheck $extensionCheck
|
||||
) {
|
||||
parent::__construct();
|
||||
$this->symfonyStyleFactory = $symfonyStyleFactory;
|
||||
$this->fileParser = $fileParser;
|
||||
$this->extensionCheck = $extensionCheck;
|
||||
}
|
||||
|
||||
protected function configure(): void
|
||||
{
|
||||
$this
|
||||
->setDescription('Checks extensions...')
|
||||
->setHelp('This command allows you to check your extensions...')
|
||||
->setDefinition(
|
||||
new InputDefinition([
|
||||
new InputArgument(self::ARG_DIRECTORY, InputArgument::OPTIONAL, "The directory to scan", "./"),
|
||||
new InputOption(self::OPT_EXCLUDE, null, InputArgument::OPTIONAL | InputOption::VALUE_IS_ARRAY, "Exclude specific extensions", []),
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$io = ($this->symfonyStyleFactory)($input, $output);
|
||||
|
||||
$io->section("Analyzing Files");
|
||||
|
||||
$scanDir = $input->getArgument(self::ARG_DIRECTORY);
|
||||
if (empty($scanDir) || !\is_string($scanDir)) {
|
||||
$scanDir = './';
|
||||
}
|
||||
|
||||
$excludedExtensionOpts = $input->getOption(self::OPT_EXCLUDE);
|
||||
$excludedExtensions = [];
|
||||
if (is_array($excludedExtensionOpts) && !empty($excludedExtensionOpts)) {
|
||||
foreach ($excludedExtensionOpts as $excludedExtensionOpt) {
|
||||
$extensions = explode(",", $excludedExtensionOpt);
|
||||
foreach ($extensions as $extension) {
|
||||
$excludedExtensions[] = \strtolower(trim($extension));
|
||||
}
|
||||
}
|
||||
$this->extensionCheck->setExcludedExtenstions($excludedExtensions);
|
||||
}
|
||||
|
||||
$this->fileParser->scanForFiles($scanDir);
|
||||
$fileCount = $this->fileParser->getFileCount();
|
||||
$io->text(sprintf('Files: %d', $fileCount));
|
||||
$io->newLine();
|
||||
|
||||
$progressBar = $this->getStyledProgressBar($output, $fileCount);
|
||||
$progressBar->start();
|
||||
|
||||
[$classes, $functions, $constants] = $this->fileParser->parseFiles(
|
||||
$this->getProgressBarClosure($progressBar)
|
||||
);
|
||||
|
||||
$progressBar->finish();
|
||||
$io->newLine(2);
|
||||
$io->text("Calls to check:");
|
||||
$io->text(sprintf(
|
||||
"%d Classes, %d Functions, %d Constants",
|
||||
count($classes),
|
||||
count($functions),
|
||||
count($constants)
|
||||
));
|
||||
|
||||
$io->section("Checking Extension Usages");
|
||||
$io->text(sprintf('Loaded Extensions: %d', $this->extensionCheck->getLoadedExtensionsCount()));
|
||||
if (!empty($excludedExtensions)) {
|
||||
$io->text(sprintf("Excluded Extensions: %s", implode(", ", $excludedExtensions)));
|
||||
}
|
||||
$io->newLine();
|
||||
$totalUsagesToCheck = count($classes) + count($functions);
|
||||
$progressBar = $this->getStyledProgressBar($output, $totalUsagesToCheck);
|
||||
$progressBar->start();
|
||||
|
||||
$usedExtensions = $this->extensionCheck->checkUsages(
|
||||
$classes,
|
||||
$functions,
|
||||
$constants,
|
||||
$this->getProgressBarClosure($progressBar)
|
||||
);
|
||||
|
||||
$progressBar->finish();
|
||||
$io->newLine(2);
|
||||
|
||||
$unusedExtensions = $this->extensionCheck->getUnused(array_keys($usedExtensions));
|
||||
$io->text(sprintf(
|
||||
"<fg=green>%d</> Used, <fg=red>%d</> Unused",
|
||||
count($usedExtensions),
|
||||
count($unusedExtensions)
|
||||
));
|
||||
|
||||
$io->section("Result");
|
||||
$io->text("Used Extensions:");
|
||||
$tmp = array_keys($usedExtensions);
|
||||
natcasesort($tmp);
|
||||
foreach ($tmp as $usedExtension) {
|
||||
$reason = $usedExtensions[$usedExtension];
|
||||
$io->text(sprintf(
|
||||
' <fg=green>%s</> %s <comment>[Usage: %s]</>',
|
||||
"\u{2713}",
|
||||
$usedExtension,
|
||||
implode(", ", array_keys($reason))
|
||||
));
|
||||
}
|
||||
|
||||
$io->newLine();
|
||||
|
||||
$io->text("Unused Extensions:");
|
||||
foreach ($unusedExtensions as $unusedExtension) {
|
||||
$io->text(sprintf(
|
||||
' <fg=red>%s</> %s',
|
||||
"\u{2717}",
|
||||
$unusedExtension
|
||||
));
|
||||
}
|
||||
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
|
||||
private function getProgressBarClosure(ProgressBar $progressBar): Closure
|
||||
{
|
||||
return function () use ($progressBar) {
|
||||
$progressBar->advance();
|
||||
};
|
||||
}
|
||||
|
||||
private function getStyledProgressBar(OutputInterface $output, int $maxValue): ProgressBar
|
||||
{
|
||||
$progressBar = new ProgressBar($output, $maxValue);
|
||||
$progressBar->setBarCharacter('<info>=</info>');
|
||||
$progressBar->setEmptyBarCharacter(' ');
|
||||
$progressBar->setProgressCharacter('>');
|
||||
|
||||
return $progressBar;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Fbrinker\ExtensionCheck\Command;
|
||||
|
||||
use Fbrinker\ExtensionCheck\Extension\ExtensionCheck;
|
||||
use Fbrinker\ExtensionCheck\Extension\ExtensionDetails;
|
||||
use Fbrinker\ExtensionCheck\Output\SymfonyStyleFactory;
|
||||
use Fbrinker\ExtensionCheck\Parser\FileParser;
|
||||
use Interop\Container\ContainerInterface;
|
||||
use Laminas\ServiceManager\Factory\FactoryInterface;
|
||||
|
||||
class CheckCommandFactory implements FactoryInterface
|
||||
{
|
||||
/**
|
||||
* @param array<mixed> $options
|
||||
*/
|
||||
public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
|
||||
{
|
||||
return new CheckCommand(
|
||||
$container->get(SymfonyStyleFactory::class),
|
||||
$container->get(FileParser::class),
|
||||
$container->get(ExtensionCheck::class)
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,103 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Fbrinker\ExtensionCheck\Extension;
|
||||
|
||||
use Closure;
|
||||
|
||||
class ExtensionCheck
|
||||
{
|
||||
private $extensionDetails;
|
||||
|
||||
/** @var string[] */
|
||||
private $excludedExtensions = [];
|
||||
|
||||
public function __construct(ExtensionDetails $extensionDetails)
|
||||
{
|
||||
$this->extensionDetails = $extensionDetails;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $excludedExtensions
|
||||
*/
|
||||
public function setExcludedExtenstions(array $excludedExtensions): void
|
||||
{
|
||||
$this->excludedExtensions = $excludedExtensions;
|
||||
}
|
||||
|
||||
public function getLoadedExtensionsCount(): int
|
||||
{
|
||||
return $this->extensionDetails->getLoadedExtensionsCount();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $usedExtensions
|
||||
* @return string[]
|
||||
*/
|
||||
public function getUnused(array $usedExtensions): array
|
||||
{
|
||||
return array_diff(
|
||||
$this->extensionDetails->getLoadedExtensions(),
|
||||
$usedExtensions,
|
||||
$this->excludedExtensions
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $classes
|
||||
* @param string[] $functions
|
||||
* @param string[] $constants
|
||||
* @return array<string,array<string,bool>>
|
||||
*/
|
||||
public function checkUsages(array $classes, array $functions, array $constants, Closure $callback)
|
||||
{
|
||||
$usedExtensions = [];
|
||||
$extensionMap = $this->extensionDetails->getExtensionMap($this->excludedExtensions);
|
||||
|
||||
foreach ($classes as $class) {
|
||||
$extension = $extensionMap->checkClass($class);
|
||||
|
||||
if (!empty($extension)) {
|
||||
$usedExtensions[$extension]['class'] = true;
|
||||
}
|
||||
|
||||
$callback();
|
||||
}
|
||||
|
||||
foreach ($functions as $function) {
|
||||
$extension = $extensionMap->checkFunction($function);
|
||||
|
||||
if (!empty($extension)) {
|
||||
$usedExtensions[$extension]['function'] = true;
|
||||
}
|
||||
|
||||
$callback();
|
||||
}
|
||||
|
||||
foreach ($constants as $constant) {
|
||||
$extension = $extensionMap->checkConstant($constant);
|
||||
|
||||
if (!empty($extension)) {
|
||||
$usedExtensions[$extension]['constant'] = true;
|
||||
}
|
||||
|
||||
$callback();
|
||||
}
|
||||
|
||||
$tmp = array_keys($usedExtensions);
|
||||
foreach ($tmp as $usedExtension) {
|
||||
$requiredExtensions = $extensionMap->checkRequiredDependency($usedExtension);
|
||||
|
||||
if (!empty($requiredExtensions)) {
|
||||
foreach ($requiredExtensions as $requiredExtension) {
|
||||
$usedExtensions[$requiredExtension]['dependency'] = true;
|
||||
}
|
||||
}
|
||||
|
||||
$callback();
|
||||
}
|
||||
|
||||
return $usedExtensions;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Fbrinker\ExtensionCheck\Extension;
|
||||
|
||||
use Interop\Container\ContainerInterface;
|
||||
use Laminas\ServiceManager\Factory\FactoryInterface;
|
||||
|
||||
class ExtensionCheckFactory implements FactoryInterface
|
||||
{
|
||||
/**
|
||||
* @param array<mixed> $options
|
||||
*/
|
||||
public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
|
||||
{
|
||||
return new ExtensionCheck(
|
||||
$container->get(ExtensionDetails::class)
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,116 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Fbrinker\ExtensionCheck\Extension;
|
||||
|
||||
use ReflectionExtension;
|
||||
|
||||
class ExtensionDetails
|
||||
{
|
||||
private $loadedExtensions;
|
||||
|
||||
/** @var ExtensionMap|null */
|
||||
private $extensionMap;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->loadedExtensions = array_map('strtolower', get_loaded_extensions());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function getLoadedExtensions(): array
|
||||
{
|
||||
natcasesort($this->loadedExtensions);
|
||||
return $this->loadedExtensions;
|
||||
}
|
||||
|
||||
public function getLoadedExtensionsCount(): int
|
||||
{
|
||||
return count($this->loadedExtensions);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $excludedExtensions
|
||||
*/
|
||||
public function getExtensionMap(array $excludedExtensions): ExtensionMap
|
||||
{
|
||||
if ($this->extensionMap === null) {
|
||||
$this->extensionMap = $this->buildExtensionMaps($excludedExtensions);
|
||||
}
|
||||
|
||||
return $this->extensionMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $excludedExtensions
|
||||
*/
|
||||
private function buildExtensionMaps(array $excludedExtensions): ExtensionMap
|
||||
{
|
||||
$extensionClasses = $extensionFunctions = $extensionConstants = $extensionDependencies = $uncheckedExtensions = [];
|
||||
foreach ($this->loadedExtensions as $loadedExtension) {
|
||||
if (in_array($loadedExtension, $excludedExtensions, true)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$extension = new ReflectionExtension($loadedExtension);
|
||||
|
||||
$classes = $extension->getClasses();
|
||||
if (!empty($classes)) {
|
||||
natcasesort($classes);
|
||||
foreach ($classes as $class) {
|
||||
$extensionClasses[$class->getName()] = $loadedExtension;
|
||||
}
|
||||
}
|
||||
|
||||
$functions = $extension->getFunctions();
|
||||
if (!empty($functions)) {
|
||||
natcasesort($functions);
|
||||
foreach ($functions as $function) {
|
||||
$extensionFunctions[$function->getName()] = $loadedExtension;
|
||||
}
|
||||
}
|
||||
|
||||
$constants = $extension->getConstants();
|
||||
if (!empty($constants)) {
|
||||
natcasesort($constants);
|
||||
foreach ($constants as $constant => $_) {
|
||||
$extensionConstants[$constant] = $loadedExtension;
|
||||
}
|
||||
}
|
||||
|
||||
$dependencies = $extension->getDependencies();
|
||||
if (!empty($dependencies)) {
|
||||
natcasesort($dependencies);
|
||||
foreach ($dependencies as $dependency => $status) {
|
||||
if (in_array($dependency, $excludedExtensions, true)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!isset($extensionDependencies[$loadedExtension][strtolower($status)])) {
|
||||
$extensionDependencies[$loadedExtension][strtolower($status)] = [];
|
||||
}
|
||||
|
||||
$extensionDependencies[$loadedExtension][strtolower($status)][] = $dependency;
|
||||
}
|
||||
}
|
||||
|
||||
// $extension->getINIEntries()
|
||||
// should we check the ini for configured extensions?
|
||||
|
||||
if (empty($classes) && empty($functions)) {
|
||||
$uncheckedExtensions[] = $extension->getName();
|
||||
}
|
||||
}
|
||||
|
||||
return new ExtensionMap(
|
||||
$extensionClasses,
|
||||
$extensionFunctions,
|
||||
$extensionConstants,
|
||||
$extensionDependencies,
|
||||
$uncheckedExtensions
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
<?php
|
||||
|
||||
namespace Fbrinker\ExtensionCheck\Extension;
|
||||
|
||||
class ExtensionMap
|
||||
{
|
||||
/** @var array<string,string> */
|
||||
public $classes;
|
||||
|
||||
/** @var array<string,string> */
|
||||
public $functions;
|
||||
|
||||
/** @var array<string,string> */
|
||||
public $constants;
|
||||
|
||||
/** @var array<string,array<string,string[]>> */
|
||||
public $dependencies;
|
||||
|
||||
/** @var string[] */
|
||||
public $unchecked;
|
||||
|
||||
/**
|
||||
* @param array<string,string> $classes
|
||||
* @param array<string,string> $functions
|
||||
* @param array<string,string> $constants
|
||||
* @param array<string,array<string,string[]>> $dependencies
|
||||
* @param string[] $unchecked
|
||||
*/
|
||||
public function __construct(
|
||||
array $classes = [],
|
||||
array $functions = [],
|
||||
array $constants = [],
|
||||
array $dependencies = [],
|
||||
array $unchecked = []
|
||||
) {
|
||||
$this->classes = $classes;
|
||||
$this->functions = $functions;
|
||||
$this->constants = $constants;
|
||||
$this->dependencies = $dependencies;
|
||||
$this->unchecked = $unchecked;
|
||||
}
|
||||
|
||||
public function checkClass(string $class): ?string
|
||||
{
|
||||
return $this->classes[$class] ?? null;
|
||||
}
|
||||
|
||||
public function checkFunction(string $function): ?string
|
||||
{
|
||||
return $this->functions[$function] ?? null;
|
||||
}
|
||||
|
||||
public function checkConstant(string $constant): ?string
|
||||
{
|
||||
return $this->constants[$constant] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function checkRequiredDependency(string $extension): array
|
||||
{
|
||||
if (!isset($this->dependencies[$extension]['required'])) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return $this->dependencies[$extension]['required'];
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
<?php
|
||||
|
||||
namespace Fbrinker\ExtensionCheck\Output;
|
||||
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
|
||||
class SymfonyStyleFactory
|
||||
{
|
||||
public function __invoke(InputInterface $input, OutputInterface $output): SymfonyStyle
|
||||
{
|
||||
return new SymfonyStyle($input, $output);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Fbrinker\ExtensionCheck\Parser;
|
||||
|
||||
use Closure;
|
||||
use Error;
|
||||
use Fbrinker\ExtensionCheck\Parser\Visitors\ClassCollector;
|
||||
use Fbrinker\ExtensionCheck\Parser\Visitors\ConstantCollector;
|
||||
use Fbrinker\ExtensionCheck\Parser\Visitors\FunctionCollector;
|
||||
use PhpParser\NodeTraverser;
|
||||
use PhpParser\ParserFactory;
|
||||
use Symfony\Component\Finder\Finder;
|
||||
use Throwable;
|
||||
|
||||
class FileParser
|
||||
{
|
||||
private $finder;
|
||||
|
||||
public function __construct(Finder $finder)
|
||||
{
|
||||
$this->finder = $finder;
|
||||
}
|
||||
|
||||
public function scanForFiles(string $directory): void
|
||||
{
|
||||
$this->finder->files()->name('*.php')->in($directory);
|
||||
$this->finder->sortByName();
|
||||
}
|
||||
|
||||
public function getFileCount(): int
|
||||
{
|
||||
return $this->finder->count();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int,string[]>
|
||||
*/
|
||||
public function parseFiles(Closure $callback): array
|
||||
{
|
||||
$parser = (new ParserFactory())->create(ParserFactory::PREFER_PHP7);
|
||||
|
||||
$traverser = new NodeTraverser();
|
||||
|
||||
$classVisitor = new ClassCollector();
|
||||
$traverser->addVisitor($classVisitor);
|
||||
|
||||
$functionVisitor = new FunctionCollector();
|
||||
$traverser->addVisitor($functionVisitor);
|
||||
|
||||
$constantCollector = new ConstantCollector();
|
||||
$traverser->addVisitor($constantCollector);
|
||||
|
||||
foreach ($this->finder as $file) {
|
||||
$content = $file->getContents();
|
||||
$stmts = [];
|
||||
|
||||
try {
|
||||
$stmts = $parser->parse($content);
|
||||
} catch (Throwable $e) {
|
||||
echo $e->getMessage();
|
||||
}
|
||||
|
||||
if (empty($stmts)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$traverser->traverse($stmts);
|
||||
$callback();
|
||||
}
|
||||
|
||||
return [
|
||||
$classVisitor->getCollected(),
|
||||
$functionVisitor->getCollected(),
|
||||
$constantCollector->getCollected(),
|
||||
];
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Fbrinker\ExtensionCheck\Parser;
|
||||
|
||||
use Interop\Container\ContainerInterface;
|
||||
use Laminas\ServiceManager\Factory\FactoryInterface;
|
||||
use Symfony\Component\Finder\Finder;
|
||||
|
||||
class FileParserFactory implements FactoryInterface
|
||||
{
|
||||
/**
|
||||
* @param array<mixed> $options
|
||||
*/
|
||||
public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
|
||||
{
|
||||
return new FileParser(
|
||||
new Finder()
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Fbrinker\ExtensionCheck\Parser\Visitors;
|
||||
|
||||
use PhpParser\Node;
|
||||
use PhpParser\NodeVisitorAbstract;
|
||||
|
||||
class ClassCollector extends NodeVisitorAbstract implements CollectorInferface
|
||||
{
|
||||
|
||||
/**
|
||||
* @var array<string,bool> $classes
|
||||
*/
|
||||
private $classes = [];
|
||||
|
||||
public function getCollected(): array
|
||||
{
|
||||
$list = array_keys($this->classes);
|
||||
natcasesort($list);
|
||||
|
||||
return array_values($list);
|
||||
}
|
||||
|
||||
public function enterNode(Node $node)
|
||||
{
|
||||
if ($node instanceof Node\Stmt\Class_) {
|
||||
if (!empty($node->extends)) {
|
||||
$this->classes[$node->extends->toString()] = true;
|
||||
}
|
||||
|
||||
if (!empty($node->implements)) {
|
||||
foreach ($node->implements as $implement) {
|
||||
$this->classes[$implement->toString()] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($node instanceof Node\Stmt\UseUse) {
|
||||
if (!empty($node->name) && $node->name instanceof Node\Name) {
|
||||
$this->classes[$node->name->toString()] = true;
|
||||
}
|
||||
}
|
||||
|
||||
if ($node instanceof Node\Expr\New_) {
|
||||
if (!empty($node->class) && $node->class instanceof Node\Name) {
|
||||
$this->classes[$node->class->toString()] = true;
|
||||
}
|
||||
}
|
||||
|
||||
if ($node instanceof Node\Expr\ClassConstFetch) {
|
||||
if (!empty($node->class) && $node->class instanceof Node\Name) {
|
||||
$this->classes[$node->class->toString()] = true;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Fbrinker\ExtensionCheck\Parser\Visitors;
|
||||
|
||||
interface CollectorInferface
|
||||
{
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function getCollected(): array;
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Fbrinker\ExtensionCheck\Parser\Visitors;
|
||||
|
||||
use PhpParser\Node;
|
||||
use PhpParser\NodeVisitorAbstract;
|
||||
|
||||
class ConstantCollector extends NodeVisitorAbstract implements CollectorInferface
|
||||
{
|
||||
/**
|
||||
* @var array<string,bool> $constants
|
||||
*/
|
||||
private $constants = [];
|
||||
|
||||
public function getCollected(): array
|
||||
{
|
||||
$list = array_keys($this->constants);
|
||||
natcasesort($list);
|
||||
|
||||
return array_values($list);
|
||||
}
|
||||
|
||||
public function enterNode(Node $node)
|
||||
{
|
||||
if ($node instanceof Node\Expr\ConstFetch) {
|
||||
if (!empty($node->name) && $node->name instanceof Node\Name) {
|
||||
$this->constants[$node->name->toString()] = true;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Fbrinker\ExtensionCheck\Parser\Visitors;
|
||||
|
||||
use PhpParser\Node;
|
||||
use PhpParser\NodeVisitorAbstract;
|
||||
|
||||
class FunctionCollector extends NodeVisitorAbstract implements CollectorInferface
|
||||
{
|
||||
/**
|
||||
* @var array<string,bool> $functions
|
||||
*/
|
||||
private $functions = [];
|
||||
|
||||
public function getCollected(): array
|
||||
{
|
||||
$list = array_keys($this->functions);
|
||||
natcasesort($list);
|
||||
|
||||
return array_values($list);
|
||||
}
|
||||
|
||||
public function enterNode(Node $node)
|
||||
{
|
||||
if ($node instanceof Node\Expr\FuncCall) {
|
||||
if (!empty($node->name) && $node->name instanceof Node\Name) {
|
||||
$this->functions[$node->name->toString()] = true;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue