Add PHP Analyzer Plugin and Composer Lock Data Handling
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled

- Implemented the PhpAnalyzerPlugin to analyze PHP projects.
- Created ComposerLockData class to represent data from composer.lock files.
- Developed ComposerLockReader to load and parse composer.lock files asynchronously.
- Introduced ComposerPackage class to encapsulate package details.
- Added PhpPackage class to represent PHP packages with metadata and evidence.
- Implemented PhpPackageCollector to gather packages from ComposerLockData.
- Created PhpLanguageAnalyzer to perform analysis and emit results.
- Added capability signals for known PHP frameworks and CMS.
- Developed unit tests for the PHP language analyzer and its components.
- Included sample composer.lock and expected output for testing.
- Updated project files for the new PHP analyzer library and tests.
This commit is contained in:
StellaOps Bot
2025-11-22 14:02:49 +02:00
parent a7f3c7869a
commit b6b9ffc050
158 changed files with 16272 additions and 809 deletions

View File

@@ -0,0 +1,83 @@
namespace StellaOps.Scanner.Analyzers.Lang.Php.Internal;
internal sealed class PhpPackage
{
private readonly ComposerPackage _package;
private readonly ComposerLockData _lockData;
public PhpPackage(ComposerPackage package, ComposerLockData lockData)
{
_package = package ?? throw new ArgumentNullException(nameof(package));
_lockData = lockData ?? throw new ArgumentNullException(nameof(lockData));
}
public string Name => _package.Name;
public string Version => _package.Version;
public string Purl => $"pkg:composer/{Name}@{Version}";
public string ComponentKey => $"purl::{Purl}";
public IEnumerable<KeyValuePair<string, string?>> CreateMetadata()
{
yield return new KeyValuePair<string, string?>("composer.dev", _package.IsDev ? "true" : "false");
if (!string.IsNullOrWhiteSpace(_package.Type))
{
yield return new KeyValuePair<string, string?>("composer.type", _package.Type);
}
if (!string.IsNullOrWhiteSpace(_package.SourceType))
{
yield return new KeyValuePair<string, string?>("composer.source.type", _package.SourceType);
}
if (!string.IsNullOrWhiteSpace(_package.SourceReference))
{
yield return new KeyValuePair<string, string?>("composer.source.ref", _package.SourceReference);
}
if (!string.IsNullOrWhiteSpace(_package.DistSha256))
{
yield return new KeyValuePair<string, string?>("composer.dist.sha256", _package.DistSha256);
}
if (!string.IsNullOrWhiteSpace(_package.DistUrl))
{
yield return new KeyValuePair<string, string?>("composer.dist.url", _package.DistUrl);
}
if (!string.IsNullOrWhiteSpace(_lockData.PluginApiVersion))
{
yield return new KeyValuePair<string, string?>("composer.plugin_api_version", _lockData.PluginApiVersion);
}
if (!string.IsNullOrWhiteSpace(_lockData.ContentHash))
{
yield return new KeyValuePair<string, string?>("composer.content_hash", _lockData.ContentHash);
}
foreach (var signal in PhpCapabilitySignals.FromPackage(_package))
{
yield return signal;
}
}
public IReadOnlyCollection<LanguageComponentEvidence> CreateEvidence()
{
var locator = string.IsNullOrWhiteSpace(_lockData.LockPath)
? "composer.lock"
: Path.GetFileName(_lockData.LockPath);
return new[]
{
new LanguageComponentEvidence(
LanguageEvidenceKind.File,
"composer.lock",
locator,
Value: $"{Name}@{Version}",
Sha256: _lockData.LockSha256)
};
}
}