ai_upgrade_assistant-0.2.0-alpha2/src/Service/PhpParserService.php
src/Service/PhpParserService.php
<?php
namespace Drupal\ai_upgrade_assistant\Service;
use PhpParser\Error;
use PhpParser\NodeTraverser;
use PhpParser\ParserFactory;
use PhpParser\Node;
use PhpParser\NodeVisitor\NameResolver;
use PhpParser\NodeFinder;
use PhpParser\PrettyPrinter;
use Drupal\Core\Logger\LoggerChannelFactoryInterface;
use Drupal\Core\File\FileSystemInterface;
use Drupal\ai_upgrade_assistant\Service\NodeVisitor\DeprecatedFunctionVisitor;
use Drupal\ai_upgrade_assistant\Service\NodeVisitor\HookVisitor;
use Drupal\ai_upgrade_assistant\Service\NodeVisitor\ClassUsageVisitor;
use Drupal\ai_upgrade_assistant\Service\NodeVisitor\DrupalDependencyVisitor;
use Drupal\ai_upgrade_assistant\Service\NodeVisitor\PluginPatternVisitor;
use Drupal\Core\DependencyInjection\DependencySerializationTrait;
/**
* Service for parsing and analyzing PHP code using PHP Parser.
*/
class PhpParserService {
use DependencySerializationTrait;
/**
* The PHP parser instance.
*
* @var \PhpParser\Parser
*/
protected $parser;
/**
* The node traverser.
*
* @var \PhpParser\NodeTraverser
*/
protected $traverser;
/**
* The node finder.
*
* @var \PhpParser\NodeFinder
*/
protected $nodeFinder;
/**
* The pretty printer.
*
* @var \PhpParser\PrettyPrinter\Standard
*/
protected $prettyPrinter;
/**
* The logger factory.
*
* @var \Drupal\Core\Logger\LoggerChannelFactoryInterface
*/
protected $loggerFactory;
/**
* The file system service.
*
* @var \Drupal\Core\File\FileSystemInterface
*/
protected $fileSystem;
/**
* The deprecated function visitor.
*
* @var \Drupal\ai_upgrade_assistant\Service\NodeVisitor\DeprecatedFunctionVisitor
*/
protected $deprecatedFunctionVisitor;
/**
* The hook visitor.
*
* @var \Drupal\ai_upgrade_assistant\Service\NodeVisitor\HookVisitor
*/
protected $hookVisitor;
/**
* The class usage visitor.
*
* @var \Drupal\ai_upgrade_assistant\Service\NodeVisitor\ClassUsageVisitor
*/
protected $classUsageVisitor;
/**
* The Drupal dependency visitor.
*
* @var \Drupal\ai_upgrade_assistant\Service\NodeVisitor\DrupalDependencyVisitor
*/
protected $dependencyVisitor;
/**
* The plugin pattern visitor.
*
* @var \Drupal\ai_upgrade_assistant\Service\NodeVisitor\PluginPatternVisitor
*/
protected $pluginPatternVisitor;
/**
* Constructs a new PhpParserService.
*
* @param \Drupal\Core\Logger\LoggerChannelFactoryInterface $logger_factory
* The logger factory service.
* @param \Drupal\Core\File\FileSystemInterface $file_system
* The file system service.
*/
public function __construct(
LoggerChannelFactoryInterface $logger_factory,
FileSystemInterface $file_system
) {
$this->loggerFactory = $logger_factory;
$this->fileSystem = $file_system;
// Initialize PHP Parser components
$this->parser = (new ParserFactory())->createForHostVersion();
// Initialize traverser with name resolver
$this->traverser = new NodeTraverser();
$this->traverser->addVisitor(new NameResolver());
// Initialize visitors
$this->deprecatedFunctionVisitor = new DeprecatedFunctionVisitor();
$this->classUsageVisitor = new ClassUsageVisitor();
$this->dependencyVisitor = new DrupalDependencyVisitor();
$this->pluginPatternVisitor = new PluginPatternVisitor();
$this->nodeFinder = new NodeFinder();
$this->prettyPrinter = new PrettyPrinter\Standard();
}
/**
* Parses a PHP file and returns its AST.
*
* @param string $file_path
* Path to the PHP file.
*
* @return \PhpParser\Node\Stmt[]|null
* Array of statements or null on error.
*/
public function parseFile(string $file_path) {
try {
// Ensure we have an absolute path
if (!$this->fileSystem->realpath($file_path)) {
// Try prepending DRUPAL_ROOT
$drupal_root = \Drupal::root();
$full_path = $drupal_root . '/' . $file_path;
if (!$this->fileSystem->realpath($full_path)) {
$this->loggerFactory->get('ai_upgrade_assistant')->error(
'File not found: @file',
['@file' => $file_path]
);
return NULL;
}
$file_path = $full_path;
}
if (!file_exists($file_path)) {
$this->loggerFactory->get('ai_upgrade_assistant')->error(
'File does not exist: @file',
['@file' => $file_path]
);
return NULL;
}
$code = file_get_contents($file_path);
if ($code === FALSE) {
$this->loggerFactory->get('ai_upgrade_assistant')->error(
'Failed to read file: @file',
['@file' => $file_path]
);
return NULL;
}
return $this->parser->parse($code);
}
catch (Error $error) {
$this->loggerFactory->get('ai_upgrade_assistant')->error(
'Parse error in @file: @message',
[
'@file' => $file_path,
'@message' => $error->getMessage(),
]
);
return NULL;
}
}
/**
* Analyzes a PHP file for deprecated code and compatibility issues.
*
* @param string $file_path
* Path to the PHP file.
* @param string $module_name
* The module name.
*
* @return array
* Analysis results containing deprecated functions, hooks, and class usage.
*/
public function analyzeFile(string $file_path, string $module_name) {
$ast = $this->parseFile($file_path);
if (!$ast) {
return [
'status' => 'error',
'message' => 'Failed to parse file',
];
}
// Reset all visitors
$this->deprecatedFunctionVisitor->reset();
$this->classUsageVisitor->reset();
$this->dependencyVisitor->reset();
$this->pluginPatternVisitor->reset();
// Create a new hook visitor for this module
$this->hookVisitor = new HookVisitor($module_name);
// Add visitors to traverser
$traverser = new NodeTraverser();
$traverser->addVisitor(new NameResolver());
$traverser->addVisitor($this->deprecatedFunctionVisitor);
$traverser->addVisitor($this->hookVisitor);
$traverser->addVisitor($this->classUsageVisitor);
$traverser->addVisitor($this->dependencyVisitor);
$traverser->addVisitor($this->pluginPatternVisitor);
// Traverse the AST
$traverser->traverse($ast);
// Get findings from all visitors
$findings = [
'status' => 'success',
'file' => $file_path,
'deprecated_functions' => $this->deprecatedFunctionVisitor->getFindings(),
'hooks' => $this->hookVisitor->getFindings(),
'class_usage' => $this->classUsageVisitor->getFindings(),
'dependencies' => $this->dependencyVisitor->getFindings(),
'plugin_patterns' => $this->pluginPatternVisitor->getFindings(),
];
// Add analysis metadata
$findings['analysis_metadata'] = [
'timestamp' => time(),
'module' => $module_name,
'php_version' => PHP_VERSION,
'patterns_found' => [
'deprecated' => count($findings['deprecated_functions']),
'hooks' => count($findings['hooks']),
'classes' => count($findings['class_usage']),
'dependencies' => count($findings['dependencies']['service_dependencies'] ?? []),
'plugins' => count($findings['plugin_patterns']['plugins'] ?? []),
],
];
return $findings;
}
/**
* Gets all deprecated items from a file.
*
* @param string $file_path
* Path to the PHP file.
* @param string $module_name
* The module name.
*
* @return array
* Array of deprecated items.
*/
public function getDeprecatedItems(string $file_path, string $module_name) {
$analysis = $this->analyzeFile($file_path, $module_name);
if ($analysis['status'] === 'error') {
return [];
}
return [
'functions' => $analysis['deprecated_functions'],
'methods' => $analysis['plugin_patterns']['deprecated_methods'] ?? [],
'classes' => array_filter(
$analysis['class_usage'],
function ($usage) {
return isset($usage['deprecated']) && $usage['deprecated'];
}
),
];
}
/**
* Gets all Drupal-specific patterns from a file.
*
* @param string $file_path
* Path to the PHP file.
* @param string $module_name
* The module name.
*
* @return array
* Array of Drupal patterns.
*/
public function getDrupalPatterns(string $file_path, string $module_name) {
$analysis = $this->analyzeFile($file_path, $module_name);
if ($analysis['status'] === 'error') {
return [];
}
return [
'hooks' => $analysis['hooks'],
'plugins' => $analysis['plugin_patterns'],
'services' => [
'dependencies' => $analysis['dependencies']['service_dependencies'] ?? [],
'static_calls' => $analysis['dependencies']['static_service_calls'] ?? [],
],
'interfaces' => $analysis['plugin_patterns']['interfaces'] ?? [],
'extends' => $analysis['plugin_patterns']['extends'] ?? [],
];
}
}
