Utilities Reference

All utilities are static classes in EasyAudit\Core\Scan\Util\. They provide shared logic used across processors for parsing, type classification, module detection, and output formatting.

Classes

File: src/Core/Scan/Util/Classes.php

Constructor parsing, class hierarchy, import resolution.

Method Signature Description
parseImportedClasses (string $fileContent): array Parse use statements into [shortName => FQCN] map. Handles aliases (use Foo as Bar).
hasImportedClass (string $class, string $fileContent): bool Check if a specific FQCN appears in the file’s imports.
hasImportedClasses (array $classes, string $fileContent): bool Check if any of the given FQCNs are imported.
parseConstructorParameters (string $fileContent): array Extract raw constructor parameter strings (e.g., ['private ProductFactory $productFactory']).
getConstructorParameterTypes (string $fileContent): array Resolve constructor params to [$paramName => $fqcn], resolving short names via imports. Skips basic types (string, int, etc.).
getInstantiation (array $constructorParams, string $trackedParam, string $fileContent): ?string Find how a constructor param is stored as a property ($this->foo). Handles promoted properties. Throws InstantiationNotFoundException if assignment is not found.
consolidateParameters (array $constructorParameters, array $importedClasses): array Resolve parameter list against import map into [$paramName => $fqcn].
resolveShortClassName (string $shortName, string $fileContent, string $namespace): string Resolve a short class name to FQCN using imports, with namespace fallback.
buildClassHierarchy (array $phpFiles): void Build internal parent-child class map from an array of PHP file paths.
getChildren (string $className): array Get child classes of a FQCN. Throws NoChildrenException if none found. Requires buildClassHierarchy() first.
extractClassName (string $fileContent): string Extract FQCN (Namespace\ClassName) from file content. Returns 'UnknownClass' on failure.
getParentConstructorParams (string $fileContent): array Extract variable names passed to parent::__construct().
resolveClassToFile (string $className): ?string Resolve a FQCN to its file path under EA_SCAN_PATH. Tries full path, then strips Vendor\Module prefix.
extendsClass (string $content, string $fqcn): bool Check if a class extends a given FQCN (handles \FQCN, imported short name, etc.).
findClassDeclarationLine (string $content, string $parentClassName): int Find line number of the class ... extends ParentClass declaration.
isFactoryClass (string $fileContent): bool Check if the file defines a class whose name ends with Factory.
isCommandClass (string $fileContent): bool Check if the file defines a Symfony Console Command subclass.
derivePropertyName (string $className): string Derive a camelCase property name from a FQCN (e.g., Magento\...\ProductFactory -> productFactory).

Example (from SpecificClassInjection):

$params = Classes::getConstructorParameterTypes($content);
foreach ($params as $paramName => $className) {
    if (Types::isCollectionType($className)) {
        // Flag direct collection injection
    }
}

Content

File: src/Core/Scan/Util/Content.php

Line number resolution, comment removal, and content extraction.

Method Signature Description
getLineNumber (string $fileContent, string $searchString): int Find the 1-based line number of the first occurrence of a string. Returns -1 if not found.
extractContent (string $fileContent, int $startLine, int $endLine): string Extract content between two 1-based line numbers (inclusive).
removeComments (string $content): string Strip block (/* */), line (//), and hash (#) comments from PHP content.
findApproximateLine (string $original, string $needle, int $approxLine, bool $normalizeWhitespace = false): int Find the actual line of a needle near an approximate line (searches +/-10 lines). Optionally normalizes whitespace for multi-line matching.

Example (from HardWrittenSQL):

$cleanedContent = Content::removeComments($fileContent);
// ... detect pattern in cleaned content, get approximate line ...
$actualLine = Content::findApproximateLine($originalContent, $match, $approxLine, true);

Functions

File: src/Core/Scan/Util/Functions.php

Function body extraction and brace-block parsing.

Method Signature Description
getFunctionContent (string $code, int $startLine): array Extract a function body starting at a given line. Returns ['content' => string, 'endLine' => int]. Uses brace counting.
getFunctionInnerContent (string $functionContent): string Extract only the inner body of a function (between its opening and closing braces).
getOccuringLineInFunction (string $functionContent, string $search): ?int Find the 1-based relative line number of a search string within function content.
extractBraceBlock (string $content, int $offset): ?string Extract the inner content of a {...} block starting at or after a given offset. Uses brace counting. Returns null if no block found.

Example (from CollectionInLoop):

// Find loop constructs, then extract their bodies
$loopBody = Functions::extractBraceBlock($cleanedContent, $loopOffset);
if ($loopBody !== null && str_contains($loopBody, '->load(')) {
    // Flag N+1 pattern
}

Types

File: src/Core/Scan/Util/Types.php

Magento type classification helpers.

Method Signature Description
isCollectionType (string $className): bool True if name contains Collection but not CollectionFactory.
isCollectionFactoryType (string $className): bool True if name contains CollectionFactory.
isRepository (string $className): bool True if name contains Repository.
isResourceModel (string $className): bool True if name contains ResourceModel.
isNonMagentoLibrary (string $className): bool True if the class belongs to a known third-party vendor (GuzzleHttp, Symfony, Laminas, etc.).
hasApiInterface (string $className): bool True if the class/interface implements an interface containing Api in its name. Uses reflection.
getApiInterface (string $className): string Return the Api interface name for a class (via reflection), or derive one by convention.
matchesSuffix (string $className, array $suffixes): bool True if the class name ends with any of the given suffixes.
matchesSubstring (string $className, array $substrings): bool True if the class name contains any of the given substrings.

Example (from SpecificClassInjection):

if (Types::isCollectionType($className)) {
    // Should inject CollectionFactory instead
}
if (Types::isNonMagentoLibrary($className)) {
    // Skip non-Magento classes
}

Modules

File: src/Core/Scan/Util/Modules.php

Module name extraction, file grouping, and path utilities.

Method Signature Description
extractModuleNameFromPath (string $filePath): ?string Extract Vendor_Module from a file path. Supports app/code/, vendor/, and generic patterns.
groupFilesByModule (array $files): array Group file paths into ['Vendor_Module' => [file1, file2, ...]].
isSameModule (string $classA, string $classB): bool True if two FQCNs share the same Vendor\Module prefix.
isBlockFile (string $file): bool True if the file is in a Block/ directory.
isSetupDirectory (string $filePath): bool True if the file is in a Setup/ directory.
findDiXmlForFile (string $phpFile): ?string Walk up from a PHP file to find the module’s etc/di.xml.

Example (from HardWrittenSQL):

// Skip Setup scripts where raw SQL is expected
if (Modules::isSetupDirectory($file)) {
    continue;
}

DiScope

File: src/Core/Scan/Util/DiScope.php

DI area scope detection for di.xml files.

Constant Value
GLOBAL 'global'
FRONTEND 'frontend'
ADMINHTML 'adminhtml'
WEBAPI_REST 'webapi_rest'
WEBAPI_SOAP 'webapi_soap'
CRONTAB 'crontab'
GRAPHQL 'graphql'
Method Signature Description
getScope (string $filePath): string Determine the area scope from a di.xml file path (e.g., etc/frontend/di.xml -> 'frontend'). Returns 'global' for etc/di.xml.
isGlobal (string $filePath): bool True if the file is in global (non-area) scope.
detectClassArea (string $className): ?string Suggest an area from a class name based on namespace patterns (e.g., \Block\Adminhtml\ -> 'adminhtml', \ViewModel\ -> 'frontend'). Returns null if no area can be inferred.
loadXml (string $file): \SimpleXMLElement\|false Load an XML file safely. Delegates to Xml::loadFile().

Example (from Preferences):

$scope = DiScope::getScope($diFile);
// Only flag duplicate preferences within the same scope

Xml

File: src/Core/Scan/Util/Xml.php

Safe XML loading with error suppression.

Method Signature Description
loadFile (string $file): \SimpleXMLElement\|false Load an XML file using simplexml_load_file() with libxml_use_internal_errors(true). Returns false on parse failure without triggering PHP warnings. Restores previous error state.

Example:

$xml = Xml::loadFile($diXmlPath);
if ($xml === false) {
    continue; // Skip malformed XML
}
foreach ($xml->preference ?? [] as $preference) {
    // Process
}

Formater

File: src/Core/Scan/Util/Formater.php

SARIF-compatible error formatting.

public static function formatError(
    string $file,
    int    $startLine,
    string $message  = '',
    string $severity = 'warning',
    int    $endLine  = 0,
    array  $metadata = []
): array

Returns:

[
    'file'      => '/absolute/path/to/file.php',
    'startLine' => 42,
    'endLine'   => 42,      // defaults to startLine if 0
    'message'   => 'Description of the issue',
    'severity'  => 'warning',
    'metadata'  => [...]     // only present if non-empty
]

The file path is resolved to an absolute path via Paths::getAbsolutePath().

Always use Formater::formatError() for all results to ensure consistent SARIF output.

Console Output (CliWriter)

File: src/Service/CliWriter.php

All console output should go through CliWriter for consistent formatting and coloring.

Result Reporting

The most important method for processors:

CliWriter::resultLine(string $label, int $count, string $severity = 'error'): void

Outputs a colored line with a severity icon:

  ✗ Hard-written SELECT queries: 3       (error - red)
  ! N+1 patterns (load in loop): 5       (warning - yellow)
  i Missing strict_types: 12             (note - blue)

Call this at the end of your process() method to report findings to the console.

Other Methods

Method Use Case
success($msg) Green message (task completed)
error($msg) Red message (failure)
warning($msg) Yellow message
info($msg) Blue message
section($title) Yellow section header
processorHeader($name) Cyan processor name (called by Scanner)
skipped($msg) Dim “skipped” indicator
line($msg) Plain text line
labelValue($label, $value, $color) Label: Value with colored value

In processors, you typically only need resultLine(). The Scanner handles processorHeader() calls automatically.