ai_upgrade_assistant-0.2.0-alpha2/src/Service/NodeVisitor/ServiceVisitor.php
src/Service/NodeVisitor/ServiceVisitor.php
<?php
namespace Drupal\ai_upgrade_assistant\Service\NodeVisitor;
use PhpParser\Node;
use PhpParser\NodeVisitorAbstract;
/**
* Node visitor that analyzes service usage and dependency injection.
*/
class ServiceVisitor extends NodeVisitorAbstract {
/**
* List of findings.
*
* @var array
*/
protected $findings = [];
/**
* List of deprecated services and their replacements.
*
* @var array
*/
protected $deprecatedServices = [
'entity.manager' => [
'replacement' => 'entity_type.manager',
'version' => '8.0.0',
'critical' => true,
],
'plugin.manager.entity' => [
'replacement' => 'entity_type.manager',
'version' => '8.0.0',
'critical' => true,
],
'string_translation' => [
'replacement' => 'string_translation',
'version' => '8.0.0',
'critical' => false,
'note' => 'Use StringTranslationTrait instead',
],
'url_generator' => [
'replacement' => 'url_generator.non_bubbling',
'version' => '9.0.0',
'critical' => false,
],
'path.alias_manager' => [
'replacement' => 'path_alias.manager',
'version' => '8.8.0',
'critical' => true,
],
'database' => [
'replacement' => 'database',
'version' => '10.0.0',
'critical' => false,
'note' => 'Use dependency injection instead of \Drupal::database()',
],
];
/**
* List of services requiring special attention in Drupal 11.
*
* @var array
*/
protected $drupal11Services = [
'entity_type.manager' => [
'changes' => [
'New methods for revision handling',
'Enhanced bundle support',
],
'example' => 'Use getRevisionableStorages() for revision-enabled entities',
],
'database' => [
'changes' => [
'New connection handling',
'Transaction improvements',
],
'example' => 'Use managed transactions with savepoints',
],
];
/**
* {@inheritdoc}
*/
public function enterNode(Node $node) {
// Check for \Drupal::service() calls
if ($node instanceof Node\Expr\StaticCall) {
if ($node->class instanceof Node\Name && $node->class->toString() === 'Drupal' && $node->name->toString() === 'service') {
if (isset($node->args[0]) && $node->args[0]->value instanceof Node\Scalar\String_) {
$service_name = $node->args[0]->value->value;
$finding = [
'type' => 'static_service',
'service' => $service_name,
'line' => $node->getLine(),
'file' => $node->getAttribute('file'),
];
if (isset($this->deprecatedServices[$service_name])) {
$finding['deprecated'] = true;
$finding['replacement'] = $this->deprecatedServices[$service_name]['replacement'];
$finding['version'] = $this->deprecatedServices[$service_name]['version'];
$finding['critical'] = $this->deprecatedServices[$service_name]['critical'];
if (isset($this->deprecatedServices[$service_name]['note'])) {
$finding['note'] = $this->deprecatedServices[$service_name]['note'];
}
}
if (isset($this->drupal11Services[$service_name])) {
$finding['drupal11_changes'] = $this->drupal11Services[$service_name];
}
$this->findings[] = $finding;
}
}
}
// Check for service container injection
elseif ($node instanceof Node\Stmt\Class_) {
$constructor = null;
$container_inject = false;
$injected_services = [];
// Check for ContainerInjectionInterface
foreach ($node->implements as $interface) {
if ($interface->toString() === 'ContainerInjectionInterface') {
$container_inject = true;
break;
}
}
// Find constructor and create method
foreach ($node->stmts as $stmt) {
if ($stmt instanceof Node\Stmt\ClassMethod) {
if ($stmt->name->toString() === '__construct') {
$constructor = $stmt;
}
elseif ($stmt->isStatic() && $stmt->name->toString() === 'create') {
foreach ($stmt->stmts as $create_stmt) {
if ($create_stmt instanceof Node\Stmt\Return_) {
if ($create_stmt->expr instanceof Node\Expr\New_) {
foreach ($create_stmt->expr->args as $arg) {
if ($arg->value instanceof Node\Expr\MethodCall) {
if ($arg->value->var instanceof Node\Expr\Variable && $arg->value->var->name === 'container' && $arg->value->name->toString() === 'get') {
if (isset($arg->value->args[0]) && $arg->value->args[0]->value instanceof Node\Scalar\String_) {
$service_name = $arg->value->args[0]->value->value;
$injected_services[] = $service_name;
}
}
}
}
}
}
}
}
}
}
if ($constructor || $container_inject || !empty($injected_services)) {
$finding = [
'type' => 'service_injection',
'class' => $node->name->toString(),
'line' => $node->getLine(),
'file' => $node->getAttribute('file'),
'has_constructor' => $constructor !== null,
'container_inject' => $container_inject,
'injected_services' => $injected_services,
];
// Check for deprecated injected services
foreach ($injected_services as $service_name) {
if (isset($this->deprecatedServices[$service_name])) {
if (!isset($finding['deprecated_services'])) {
$finding['deprecated_services'] = [];
}
$finding['deprecated_services'][] = [
'service' => $service_name,
'replacement' => $this->deprecatedServices[$service_name]['replacement'],
'version' => $this->deprecatedServices[$service_name]['version'],
'critical' => $this->deprecatedServices[$service_name]['critical'],
];
}
}
// Check for D11 service changes
foreach ($injected_services as $service_name) {
if (isset($this->drupal11Services[$service_name])) {
if (!isset($finding['drupal11_services'])) {
$finding['drupal11_services'] = [];
}
$finding['drupal11_services'][] = [
'service' => $service_name,
'changes' => $this->drupal11Services[$service_name],
];
}
}
$this->findings[] = $finding;
}
}
}
/**
* Gets the findings.
*
* @return array
* Array of findings.
*/
public function getFindings() {
return $this->findings;
}
/**
* Resets the findings.
*/
public function reset() {
$this->findings = [];
}
}
