Practical guide to generating Software Bills of Materials for PHP applications with Composer-based dependency analysis, validation, and security review.

Updated:

PHP SBOM Guide: Composer Dependencies and Security

Quick Answer

For most Composer-based PHP applications, generate the SBOM from composer.lock, then validate the file before sharing it with security, compliance, or procurement teams. Use a container or filesystem scanner such as Syft when the deployed PHP app also includes WordPress plugins, themes, system packages, frontend assets, or container image dependencies outside Composer.

A practical PHP SBOM workflow is:

  1. generate package inventory from Composer or the deployed container
  2. choose CycloneDX for security automation or SPDX for license-heavy workflows
  3. validate the result with the SBOM Validator
  4. store the SBOM with the release artifact or deployment image

Introduction: PHP's Complex Dependency Landscape

PHP's ecosystem, powered by Composer's dependency management model, now spans traditional CMS deployments, modern frameworks, APIs, and containerized workloads. That breadth creates a real inventory problem: a typical Laravel application can pull in 100+ direct and transitive packages, while WordPress and Drupal deployments often mix core code, plugins, themes, and custom modules from different sources.

The challenge is not just generating an SBOM, but generating one that reflects how the application is actually built and deployed. PHP workloads may run on shared hosting, containers, managed platforms, or hybrid stacks with frontend and infrastructure dependencies outside composer.lock. Legacy PHP 5.x and 7.x estates often sit beside newer PHP 8.x services, so teams need a workflow that works across both modern Composer projects and older applications. From Laravel and Symfony to WordPress and Drupal, PHP applications depend heavily on third-party packages, making dependency visibility essential for security reviews, license tracking, and procurement requests. This guide focuses on practical PHP SBOM generation, when to use CycloneDX vs SPDX, and how to validate the output with the SBOM Validator before wiring it into CI/CD.

Why PHP SBOM Generation Is Critical

Understanding PHP's Unique Security Challenges

PHP powers approximately 78% of websites with known server-side languages, including major platforms like WordPress (43% of all websites), Facebook, and Wikipedia. This massive footprint makes PHP applications attractive targets for attackers, and the language's history of security vulnerabilities adds urgency to supply chain security efforts. Recent supply chain attacks targeting PHP packages have demonstrated that even popular, well-maintained packages can be compromised.

The PHP Ecosystem Landscape

๐ŸŒ Massive Package Ecosystem
  • Packagist.org: 380,000+ packages with billions of downloads
  • Complex Dependency Trees: Deep transitive dependencies through Composer
  • Framework Ecosystems: Laravel (50+ first-party packages), Symfony (60+ components)
  • Legacy Systems: WordPress plugins, Drupal modules, custom frameworks
๐Ÿ” Security Challenges
  • Supply Chain Attacks: Package takeovers and malicious updates
  • Vulnerability Propagation: Transitive dependencies with security flaws
  • Outdated Dependencies: Long-lived applications with stale packages
  • Dev vs. Production: Different dependency sets for different environments
๐Ÿ“Š Compliance Requirements
  • Open Source License Tracking: GPL, MIT, Apache licenses in complex combinations
  • Commercial License Management: Premium packages and proprietary code
  • Regulatory Compliance: GDPR, HIPAA, PCI-DSS requirements for web applications
  • Audit Trails: Change tracking for financial and healthcare applications

Business Impact for PHP Organizations

โšก Enhanced Security Posture
  • Rapid Vulnerability Response: Identify affected packages in minutes vs. hours
  • Supply Chain Attack Prevention: Monitor for suspicious package changes
  • Compliance Automation: Automated license and security compliance checking
  • Risk Assessment: Quantify third-party dependency risks
๐Ÿ’ฐ Operational Benefits
  • Dependency Optimization: Identify unused or redundant packages
  • Update Planning: Strategic dependency update roadmaps
  • License Cost Management: Track commercial license usage
  • Technical Debt Reduction: Systematic legacy dependency cleanup

๐Ÿ› ๏ธ Tool Ecosystem Overview

Selecting the Right SBOM Tool for PHP

Choosing the appropriate SBOM generation tool for PHP depends on your application architecture, deployment model, and compliance requirements. Composer-based applications benefit from tools that understand composer.json and composer.lock files, while legacy applications might require filesystem scanning approaches. Understanding each tool's strengths helps you build a comprehensive SBOM strategy.

PHP-Specific SBOM Tools

ToolApproachStrengthsBest Use Case
CycloneDX PHP ComposerComposer.lock parsingNative Composer integrationModern PHP applications
SyftFilesystem scanningMulti-language supportContainerized PHP apps
PHP Security CheckerSecurity-focusedVulnerability detectionSecurity analysis
Composer AuditBuilt-in securityNative Composer toolDevelopment workflow

Cross-Platform Integration

ToolPHP Support QualityIntegration LevelEnterprise Features
FOSSAโญโญโญโญโญDeep Composer analysisPolicy management
SnykโญโญโญโญโญReal-time monitoringFix automation
MendโญโญโญโญGood package detectionLicense compliance
JFrog XrayโญโญโญโญArtifact analysisDevOps integration

๐Ÿ“ฆ Installation and Setup

CycloneDX PHP Composer Plugin

Global Installation (Recommended)
# Install globally via Composer
composer global require cyclonedx/cyclonedx-php-composer

# Ensure global Composer bin is in PATH
export PATH="$PATH:$HOME/.composer/vendor/bin"

# Verify the Composer command is available
composer CycloneDX:make-sbom --help
Local Project Installation
# Install as development dependency
composer require --dev cyclonedx/cyclonedx-php-composer

# Use via Composer scripts
composer CycloneDX:make-sbom --help

# Add to composer.json scripts
cat >> composer.json << 'EOF'
{
  "scripts": {
    "sbom": "composer CycloneDX:make-sbom",
    "sbom:json": "composer CycloneDX:make-sbom --output-format=JSON --output-file=sbom.json",
    "sbom:xml": "composer CycloneDX:make-sbom --output-format=XML --output-file=sbom.xml"
  }
}
EOF

Alternative Tools Setup

Syft for PHP Projects
# Install Syft
curl -sSfL https://get.anchore.io/syft | sh -s -- -b /usr/local/bin

# Test PHP detection
syft . --catalogers php-composer-cataloger
PHP Security Checker
# Install security checker
composer global require sensiolabs/security-checker

# Or use the new Symfony CLI (recommended)
wget https://get.symfony.com/cli/installer -O - | bash
sudo mv ~/.symfony/bin/symfony /usr/local/bin/symfony
SPDX Tools for PHP
# SPDX tooling is format-oriented rather than Composer-specific
pip install spdx-tools

Development Environment Integration

PhpStorm Integration
<!-- .idea/runConfigurations/Generate_SBOM.xml -->
<component name="ProjectRunConfigurationManager">
  <configuration default="false" name="Generate SBOM" type="PhpLocalRunConfigurationType" factoryName="PHP Console">
    <option name="commandLineParameters" value="CycloneDX:make-sbom --output-format=JSON --output-file=sbom.json" />
    <option name="interpreterOptions" value="" />
    <option name="pathToPhpExecutable" value="" />
    <option name="pathToScript" value="composer" />
    <option name="workingDirectory" value="$PROJECT_DIR$" />
    <method v="2" />
  </configuration>
</component>
VS Code Integration
// .vscode/tasks.json
{
    "version": "2.0.0",
    "tasks": [
        {
            "label": "Generate PHP SBOM",
            "type": "shell",
            "command": "composer",
            "args": ["run-script", "sbom:json"],
            "group": "build",
            "presentation": {
                "echo": true,
                "reveal": "always",
                "focus": false,
                "panel": "shared",
                "clear": true
            },
            "problemMatcher": []
        },
        {
            "label": "Security Audit",
            "type": "shell", 
            "command": "composer",
            "args": ["audit"],
            "group": "test",
            "presentation": {
                "echo": true,
                "reveal": "always"
            }
        }
    ]
}
Git Hooks Integration
#!/bin/bash
# .git/hooks/pre-commit - Generate SBOM before commits

echo "๐Ÿ” Generating PHP SBOM..."

# Check if composer.json has changed
if git diff --cached --name-only | grep -q "composer.json\|composer.lock"; then
    echo "๐Ÿ“ฆ Composer files changed, generating new SBOM..."

    # Generate SBOM
    composer run-script sbom:json

    # Add SBOM to commit
    git add sbom.json

    echo "โœ… SBOM generated and added to commit"
else
    echo "โ„น๏ธ  No Composer changes detected"
fi

๐ŸŽฏ Basic Usage Patterns

Simple Project SBOM Generation

Basic Laravel Application
# Navigate to Laravel project
cd my-laravel-app

# Generate basic SBOM
composer CycloneDX:make-sbom

# Generate with specific format and output
composer CycloneDX:make-sbom \
  --output-format=JSON \
  --output-file=laravel-sbom.json

# Generate a production-focused SBOM
composer CycloneDX:make-sbom \
  --output-format=JSON \
  --output-file=laravel-production-sbom.json \
  --omit=dev
Symfony Application
# Generate Symfony SBOM
cd symfony-project

composer CycloneDX:make-sbom \
  --output-format=JSON \
  --output-file=symfony-sbom.json
WordPress with Composer
# Modern WordPress with Composer (using Bedrock or similar)
cd wordpress-site

# Generate SBOM including WordPress core and plugins
composer CycloneDX:make-sbom \
  --output-format=JSON \
  --output-file=wordpress-sbom.json

Framework-Specific Examples

Laravel Project with Multiple Environments
// composer.json for Laravel with environment-specific packages
{
    "name": "company/laravel-app",
    "type": "project",
    "require": {
        "php": "^8.1",
        "laravel/framework": "^10.0",
        "laravel/sanctum": "^3.2",
        "laravel/tinker": "^2.8",
        "guzzlehttp/guzzle": "^7.2",
        "intervention/image": "^2.7",
        "predis/predis": "^2.0"
    },
    "require-dev": {
        "fakerphp/faker": "^1.9.1",
        "laravel/pint": "^1.0",
        "laravel/sail": "^1.18",
        "mockery/mockery": "^1.4.4",
        "nunomaduro/collision": "^7.0",
        "phpunit/phpunit": "^10.0",
        "spatie/laravel-ignition": "^2.0"
    },
    "scripts": {
        "sbom:production": "composer CycloneDX:make-sbom --output-format=JSON --output-file=production-sbom.json --omit=dev",
        "sbom:development": "composer CycloneDX:make-sbom --output-format=JSON --output-file=development-sbom.json",
        "sbom:all": ["@sbom:production", "@sbom:development"],
        "security:check": "composer audit",
        "security:full": [
            "@security:check",
            "@sbom:production",
            "grype sbom:production-sbom.json"
        ]
    }
}
# Generate environment-specific SBOMs
composer run sbom:production
composer run sbom:development

# Run full security analysis
composer run security:full
Symfony Bundle Development
# Generate SBOM for Symfony bundle
cd symfony-bundle

# Bundle-specific SBOM with metadata
composer CycloneDX:make-sbom \
  --output-format=JSON \
  --output-file=bundle-sbom.json \
  --omit=dev

Advanced Composer Scenarios

Monorepo with Multiple PHP Applications
#!/bin/bash
# generate-monorepo-sboms.sh - Handle PHP monorepo SBOM generation

MONOREPO_ROOT="$1"
OUTPUT_DIR="${2:-sboms}"

if [ -z "$MONOREPO_ROOT" ]; then
    echo "Usage: $0 <monorepo-root> [output-dir]"
    exit 1
fi

echo "๐Ÿข PHP Monorepo SBOM Generation"
echo "=============================="

mkdir -p "$OUTPUT_DIR"

# Find all composer.json files
find "$MONOREPO_ROOT" -name "composer.json" -type f | while read composer_file; do
    project_dir=$(dirname "$composer_file")
    project_name=$(basename "$project_dir")

    echo "๐Ÿ“ฆ Processing: $project_name"

    cd "$project_dir"

    # Skip if no composer.lock (not installed)
    if [ ! -f "composer.lock" ]; then
        echo "  โš ๏ธ  No composer.lock found, skipping"
        continue
    fi

    # Generate SBOM
    output_file="$OUTPUT_DIR/${project_name}-sbom.json"

    if composer CycloneDX:make-sbom \
        --output-format=JSON \
        --output-file="$output_file"; then

        # Validate generated SBOM
        component_count=$(jq '.components | length' "$output_file" 2>/dev/null || echo "0")
        echo "  โœ… Generated SBOM with $component_count components"

        # Add monorepo context
        jq --arg repo_root "$MONOREPO_ROOT" --arg subproject "$project_name" \
           '.metadata.properties += [
               {"name": "monorepo.root", "value": $repo_root},
               {"name": "monorepo.subproject", "value": $subproject}
           ]' "$output_file" > "${output_file}.tmp" && mv "${output_file}.tmp" "$output_file"
    else
        echo "  โŒ Failed to generate SBOM"
    fi

    cd - > /dev/null
done

echo ""
echo "๐Ÿ“Š Monorepo SBOM Summary:"
ls -la "$OUTPUT_DIR"/*.json | wc -l | xargs echo "Generated SBOMs:"
find "$OUTPUT_DIR" -name "*.json" -exec jq -r '.components | length' {} \; | awk '{sum+=$1} END {print "Total components: " sum}'
Private Package Repository Integration
// composer.json with private repositories
{
    "repositories": [
        {
            "type": "composer",
            "url": "https://repo.packagist.com/company/"
        },
        {
            "type": "vcs",
            "url": "git@gitlab.company.com:php/internal-lib.git"
        },
        {
            "type": "path",
            "url": "./local-packages/*"
        }
    ],
    "require": {
        "company/internal-package": "^2.0",
        "company/another-lib": "dev-main",
        "local/custom-component": "@dev"
    }
}
# Generate SBOM with private repository context
composer CycloneDX:make-sbom \
  --output-format=JSON \
  --output-file=private-sbom.json

# Enhance SBOM with repository information
jq '.components[] |= (
  if .purl then
    .properties += [{"name": "repository.type", "value": "private"}]
  else . end
)' private-sbom.json > enhanced-sbom.json

๐Ÿ—๏ธ Framework-Specific Integration

Laravel Deep Integration

Laravel Service Provider for SBOM
<?php
// app/Providers/SbomServiceProvider.php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use Illuminate\Console\Scheduling\Schedule;

class SbomServiceProvider extends ServiceProvider
{
    public function register()
    {
        $this->commands([
            \App\Console\Commands\GenerateSbom::class,
            \App\Console\Commands\SecurityAudit::class,
        ]);
    }

    public function boot()
    {
        $this->publishes([
            __DIR__.'/../../config/sbom.php' => config_path('sbom.php'),
        ], 'sbom-config');

        // Schedule automatic SBOM generation
        $this->app->booted(function () {
            $schedule = $this->app->make(Schedule::class);

            // Generate SBOM weekly
            $schedule->command('sbom:generate')
                ->weekly()
                ->sundays()
                ->at('02:00')
                ->withoutOverlapping();

            // Security audit daily
            $schedule->command('security:audit')
                ->daily()
                ->at('06:00');
        });
    }
}
Laravel Artisan Commands
<?php
// app/Console/Commands/GenerateSbom.php

namespace App\Console\Commands;

use Illuminate\Console\Command;
use Symfony\Component\Process\Process;
use Illuminate\Support\Facades\Storage;

class GenerateSbom extends Command
{
    protected $signature = 'sbom:generate 
                           {--format=json : Output format (json|xml)}
                           {--include-dev : Include development dependencies}
                           {--upload : Upload to configured storage}';

    protected $description = 'Generate SBOM for the Laravel application';

    public function handle()
    {
        $this->info('๐Ÿ” Generating Laravel SBOM...');

        $format = $this->option('format');
        $includeDev = $this->option('include-dev');
        $upload = $this->option('upload');

        // Build command
        $command = [
            'composer',
            'CycloneDX:make-sbom',
            '--output-format=' . $format,
            '--output-file=storage/app/sbom.' . $format
        ];

        if (!$includeDev) {
            $command[] = '--omit=dev';
        }

        // Execute SBOM generation
        $process = new Process($command, base_path());
        $process->run();

        if (!$process->isSuccessful()) {
            $this->error('Failed to generate SBOM: ' . $process->getErrorOutput());
            return 1;
        }

        $this->info('โœ… SBOM generated successfully');

        // Parse and display summary
        $sbomPath = storage_path('app/sbom.' . $format);
        $this->displaySbomSummary($sbomPath);

        // Upload if requested
        if ($upload) {
            $this->uploadSbom($sbomPath);
        }

        return 0;
    }

    private function getAppVersion(): string
    {
        // Try to get version from git
        $process = new Process(['git', 'describe', '--tags', '--abbrev=0'], base_path());
        $process->run();

        if ($process->isSuccessful()) {
            return trim($process->getOutput());
        }

        // Fallback to config or composer.json
        return config('app.version', '1.0.0');
    }

    private function displaySbomSummary(string $sbomPath): void
    {
        if (!file_exists($sbomPath)) {
            return;
        }

        $sbom = json_decode(file_get_contents($sbomPath), true);

        $componentCount = count($sbom['components'] ?? []);
        $this->info("๐Ÿ“ฆ Components: {$componentCount}");

        // License summary
        $licenses = [];
        foreach ($sbom['components'] ?? [] as $component) {
            foreach ($component['licenses'] ?? [] as $license) {
                $licenseName = $license['license']['name'] ?? $license['license']['id'] ?? 'Unknown';
                $licenses[$licenseName] = ($licenses[$licenseName] ?? 0) + 1;
            }
        }

        if ($licenses) {
            $this->info('๐Ÿ“„ License distribution:');
            arsort($licenses);
            foreach (array_slice($licenses, 0, 5, true) as $license => $count) {
                $this->line("  โ€ข {$license}: {$count}");
            }
        }
    }

    private function uploadSbom(string $sbomPath): void
    {
        if (!config('sbom.storage.enabled')) {
            $this->warn('SBOM upload not configured');
            return;
        }

        $this->info('๐Ÿ“ค Uploading SBOM...');

        $disk = Storage::disk(config('sbom.storage.disk', 's3'));
        $filename = 'sboms/' . config('app.name') . '/' . date('Y-m-d-H-i-s') . '-sbom.json';

        if ($disk->put($filename, file_get_contents($sbomPath))) {
            $this->info("โœ… SBOM uploaded to: {$filename}");
        } else {
            $this->error('Failed to upload SBOM');
        }
    }
}
Laravel Configuration
<?php
// config/sbom.php

return [
    'enabled' => env('SBOM_ENABLED', true),

    'generation' => [
        'format' => env('SBOM_FORMAT', 'json'),
        'include_dev' => env('SBOM_INCLUDE_DEV', false),
        'component_name' => env('APP_NAME', 'Laravel App'),
        'component_version' => env('APP_VERSION', '1.0.0'),
    ],

    'storage' => [
        'enabled' => env('SBOM_STORAGE_ENABLED', false),
        'disk' => env('SBOM_STORAGE_DISK', 's3'),
        'path' => env('SBOM_STORAGE_PATH', 'sboms'),
    ],

    'security' => [
        'audit_enabled' => env('SBOM_SECURITY_AUDIT', true),
        'vulnerability_sources' => [
            'composer_audit',
            'snyk',
            'grype'
        ],
    ],

    'compliance' => [
        'frameworks' => ['SOX', 'PCI-DSS'],
        'license_policy' => storage_path('sbom/license-policy.json'),
    ],
];

Symfony Bundle Integration

Symfony Bundle for SBOM Management
<?php
// src/Bundle/SbomBundle/SbomBundle.php

namespace App\Bundle\SbomBundle;

use Symfony\Component\HttpKernel\Bundle\Bundle;
use Symfony\Component\DependencyInjection\ContainerBuilder;

class SbomBundle extends Bundle
{
    public function build(ContainerBuilder $container): void
    {
        parent::build($container);

        $container->addCompilerPass(new SbomCompilerPass());
    }
}
<?php
// src/Bundle/SbomBundle/Command/SbomGenerateCommand.php

namespace App\Bundle\SbomBundle\Command;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\Process\Exception\ProcessFailedException;
use Symfony\Component\Process\Process;

class SbomGenerateCommand extends Command
{
    protected static $defaultName = 'app:sbom:generate';
    protected static $defaultDescription = 'Generate SBOM for Symfony application';

    private string $projectDir;

    public function __construct(string $projectDir)
    {
        $this->projectDir = $projectDir;
        parent::__construct();
    }

    protected function configure(): void
    {
        $this
            ->addOption('format', 'f', InputOption::VALUE_REQUIRED, 'Output format', 'json')
            ->addOption('output', 'o', InputOption::VALUE_REQUIRED, 'Output file', 'sbom.json')
            ->addOption('include-dev', null, InputOption::VALUE_NONE, 'Include dev dependencies')
            ->addOption('validate', null, InputOption::VALUE_NONE, 'Validate generated SBOM');
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $io = new SymfonyStyle($input, $output);

        $io->title('Symfony SBOM Generator');

        $format = $input->getOption('format');
        $outputFile = $input->getOption('output');
        $includeDev = $input->getOption('include-dev');
        $validate = $input->getOption('validate');

        // Build generation command
        $command = [
            'composer',
            'CycloneDX:make-sbom',
            '--output-format=' . $format,
            '--output-file=' . $outputFile
        ];

        if (!$includeDev) {
            $command[] = '--omit=dev';
        }

        $io->section('Generating SBOM...');

        try {
            $process = new Process($command, $this->projectDir);
            $process->mustRun();

            $io->success('SBOM generated successfully: ' . $outputFile);

            // Display SBOM statistics
            $this->displayStats($io, $outputFile);

            // Validate if requested
            if ($validate) {
                $this->validateSbom($io, $outputFile);
            }

        } catch (ProcessFailedException $exception) {
            $io->error('Failed to generate SBOM: ' . $exception->getMessage());
            return Command::FAILURE;
        }

        return Command::SUCCESS;
    }

    private function displayStats(SymfonyStyle $io, string $sbomFile): void
    {
        if (!file_exists($sbomFile)) {
            return;
        }

        $sbom = json_decode(file_get_contents($sbomFile), true);

        $stats = [
            'Components' => count($sbom['components'] ?? []),
            'Format' => $sbom['bomFormat'] ?? 'Unknown',
            'Spec Version' => $sbom['specVersion'] ?? 'Unknown',
            'Generated' => $sbom['metadata']['timestamp'] ?? 'Unknown'
        ];

        $io->definitionList(...array_map(
            fn($key, $value) => [$key, $value],
            array_keys($stats),
            array_values($stats)
        ));
    }

    private function validateSbom(SymfonyStyle $io, string $sbomFile): void
    {
        $io->section('Validating SBOM...');

        try {
            // Basic JSON validation
            $sbom = json_decode(file_get_contents($sbomFile), true);
            if (json_last_error() !== JSON_ERROR_NONE) {
                throw new \InvalidArgumentException('Invalid JSON: ' . json_last_error_msg());
            }

            // Required fields validation
            $requiredFields = ['bomFormat', 'specVersion', 'serialNumber', 'version'];
            foreach ($requiredFields as $field) {
                if (!isset($sbom[$field])) {
                    throw new \InvalidArgumentException("Missing required field: {$field}");
                }
            }

            $io->success('SBOM validation passed');

        } catch (\Exception $e) {
            $io->error('SBOM validation failed: ' . $e->getMessage());
        }
    }
}

WordPress Modern Setup

WordPress with Composer (Bedrock-style)
// composer.json for modern WordPress
{
    "name": "company/wordpress-site",
    "type": "project",
    "repositories": [
        {
            "type": "composer",
            "url": "https://wpackagist.org",
            "only": ["wpackagist-plugin/*", "wpackagist-theme/*"]
        }
    ],
    "require": {
        "php": ">=8.1",
        "johnpbloch/wordpress-core": "6.4.2",
        "vlucas/phpdotenv": "^5.5",
        "oscarotero/env": "^2.1",
        "roots/wp-password-bcrypt": "1.1.0",

        "wpackagist-plugin/advanced-custom-fields": "^6.2",
        "wpackagist-plugin/yoast-seo": "^21.8",
        "wpackagist-plugin/wordfence": "^7.11",

        "wpackagist-theme/twentytwentyfour": "^1.0"
    },
    "require-dev": {
        "squizlabs/php_codesniffer": "^3.7",
        "wp-coding-standards/wpcs": "^3.0",
        "phpcompatibility/phpcompatibility-wp": "^2.1"
    },
    "scripts": {
        "sbom:generate": "composer CycloneDX:make-sbom --output-format=JSON --output-file=wordpress-sbom.json",
        "sbom:production": "composer CycloneDX:make-sbom --output-format=JSON --output-file=production-sbom.json --omit=dev",
        "security:check": "composer audit --no-dev",
        "wp:update": [
            "composer update",
            "@sbom:production"
        ]
    }
}
# WordPress-specific SBOM generation
composer run sbom:generate

# Enhance WordPress SBOM with plugin information
php -r "
$sbom = json_decode(file_get_contents('wordpress-sbom.json'), true);

// Add WordPress context
$sbom['metadata']['properties'][] = [
    'name' => 'wordpress.version',
    'value' => '6.4.2'
];

$sbom['metadata']['properties'][] = [
    'name' => 'wordpress.type',
    'value' => 'composer-managed'
];

// Categorize components
foreach (\$sbom['components'] as &\$component) {
    if (strpos(\$component['name'], 'wpackagist-plugin/') === 0) {
        \$component['properties'][] = [
            'name' => 'wordpress.type',
            'value' => 'plugin'
        ];
    } elseif (strpos(\$component['name'], 'wpackagist-theme/') === 0) {
        \$component['properties'][] = [
            'name' => 'wordpress.type', 
            'value' => 'theme'
        ];
    } elseif (\$component['name'] === 'johnpbloch/wordpress-core') {
        \$component['properties'][] = [
            'name' => 'wordpress.type',
            'value' => 'core'
        ];
    }
}

file_put_contents('wordpress-enhanced-sbom.json', json_encode(\$sbom, JSON_PRETTY_PRINT));
echo \"WordPress SBOM enhanced with plugin/theme metadata\n\";
"

๐Ÿš€ CI/CD Pipeline Integration

GitHub Actions for PHP

Comprehensive PHP SBOM Workflow
# .github/workflows/php-sbom.yml
name: PHP SBOM Generation and Security

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]
  schedule:
    - cron: '0 6 * * 1'  # Weekly Monday 6 AM

env:
  PHP_VERSION: '8.2'
  COMPOSER_CACHE_DIR: ~/.composer/cache

jobs:
  sbom-generation:
    runs-on: ubuntu-latest

    outputs:
      sbom-file: ${{ steps.generate.outputs.sbom-file }}
      component-count: ${{ steps.generate.outputs.component-count }}

    steps:
    - name: Checkout code
      uses: actions/checkout@v4

    - name: Setup PHP
      uses: shivammathur/setup-php@v2
      with:
        php-version: ${{ env.PHP_VERSION }}
        extensions: mbstring, xml, ctype, iconv, intl, pdo_sqlite, dom, filter, gd, json, pcre, hash
        tools: composer:v2

    - name: Get Composer cache directory
      id: composer-cache
      run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT

    - name: Cache Composer dependencies
      uses: actions/cache@v3
      with:
        path: ${{ steps.composer-cache.outputs.dir }}
        key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
        restore-keys: ${{ runner.os }}-composer-

    - name: Install dependencies
      run: composer install --prefer-dist --no-interaction --no-scripts

    - name: Install CycloneDX PHP Composer
      run: composer global require cyclonedx/cyclonedx-php-composer

    - name: Add Composer global bin to PATH
      run: echo "$HOME/.composer/vendor/bin" >> $GITHUB_PATH

    - name: Generate SBOM
      id: generate
      run: |
        # Determine project name and version
        PROJECT_NAME=$(jq -r '.name // "php-application"' composer.json)

        if [[ "${{ github.ref }}" == refs/tags/* ]]; then
          VERSION=${GITHUB_REF#refs/tags/}
        else
          VERSION="${{ github.sha }}"
        fi

        echo "Generating SBOM for $PROJECT_NAME version $VERSION"

        # Generate production SBOM
        composer CycloneDX:make-sbom \
          --output-format=JSON \
          --output-file=sbom-production.json \
          --omit=dev

        # Generate development SBOM
        composer CycloneDX:make-sbom \
          --output-format=JSON \
          --output-file=sbom-development.json

        # Output results
        COMPONENT_COUNT=$(jq '.components | length' sbom-production.json)
        echo "sbom-file=sbom-production.json" >> $GITHUB_OUTPUT
        echo "component-count=$COMPONENT_COUNT" >> $GITHUB_OUTPUT

        echo "โœ… Generated SBOMs with $COMPONENT_COUNT production components"

    - name: Validate SBOMs
      run: |
        echo "๐Ÿ” Validating generated SBOMs..."

        for sbom in sbom-*.json; do
          echo "Validating $sbom"

          # JSON validation
          if ! jq empty "$sbom"; then
            echo "โŒ Invalid JSON in $sbom"
            exit 1
          fi

          # Basic structure validation
          required_fields=("bomFormat" "specVersion" "serialNumber" "version" "metadata" "components")

          for field in "${required_fields[@]}"; do
            if ! jq -e ".$field" "$sbom" > /dev/null; then
              echo "โŒ Missing required field '$field' in $sbom"
              exit 1
            fi
          done

          # Component validation
          component_count=$(jq '.components | length' "$sbom")
          if [[ "$component_count" -eq 0 ]]; then
            echo "โš ๏ธ Warning: No components found in $sbom"
          else
            echo "โœ… $sbom: Valid with $component_count components"
          fi
        done

    - name: Generate SBOM summary
      run: |
        echo "๐Ÿ“Š SBOM Generation Summary" > sbom-summary.md
        echo "=========================" >> sbom-summary.md
        echo "" >> sbom-summary.md

        echo "**Repository**: ${{ github.repository }}" >> sbom-summary.md
        echo "**Branch**: ${{ github.ref_name }}" >> sbom-summary.md
        echo "**Commit**: ${{ github.sha }}" >> sbom-summary.md
        echo "**Generated**: $(date -u +'%Y-%m-%d %H:%M:%S UTC')" >> sbom-summary.md
        echo "" >> sbom-summary.md

        echo "### Production Dependencies" >> sbom-summary.md
        PROD_COUNT=$(jq '.components | length' sbom-production.json)
        echo "- **Components**: $PROD_COUNT" >> sbom-summary.md

        echo "### Development Dependencies" >> sbom-summary.md
        DEV_COUNT=$(jq '.components | length' sbom-development.json)
        echo "- **Components**: $DEV_COUNT" >> sbom-summary.md

        echo "" >> sbom-summary.md
        echo "### License Distribution" >> sbom-summary.md

        # Extract licenses and count them
        jq -r '.components[]?.licenses[]?.license.name // .components[]?.licenses[]?.license.id // "Unknown"' sbom-production.json | \
        sort | uniq -c | sort -nr | head -10 | \
        while read count license; do
          echo "- **$license**: $count packages" >> sbom-summary.md
        done

    - name: Upload SBOM artifacts
      uses: actions/upload-artifact@v4
      with:
        name: php-sboms
        path: |
          sbom-*.json
          sbom-summary.md
        retention-days: 30

  security-analysis:
    runs-on: ubuntu-latest
    needs: sbom-generation

    steps:
    - name: Checkout code
      uses: actions/checkout@v4

    - name: Setup PHP
      uses: shivammathur/setup-php@v2
      with:
        php-version: ${{ env.PHP_VERSION }}
        tools: composer:v2

    - name: Install dependencies
      run: composer install --prefer-dist --no-interaction

    - name: Download SBOM artifacts
      uses: actions/download-artifact@v4
      with:
        name: php-sboms
        path: ./

    - name: Composer Security Audit
      id: composer-audit
      run: |
        echo "๐Ÿ” Running Composer security audit..."

        # Run composer audit and capture output
        if composer audit --format=json > composer-audit.json 2>&1; then
          echo "audit-status=passed" >> $GITHUB_OUTPUT
          echo "โœ… No known security vulnerabilities found"
        else
          echo "audit-status=failed" >> $GITHUB_OUTPUT
          echo "โš ๏ธ Security vulnerabilities detected"
        fi

        # Parse audit results
        if [[ -f composer-audit.json ]]; then
          VULN_COUNT=$(jq '.advisories | length' composer-audit.json 2>/dev/null || echo "0")
          echo "vulnerability-count=$VULN_COUNT" >> $GITHUB_OUTPUT
        else
          echo "vulnerability-count=0" >> $GITHUB_OUTPUT
        fi

    - name: Install Grype for SBOM scanning
      run: |
        curl -sSfL https://raw.githubusercontent.com/anchore/grype/main/install.sh | sh -s -- -b /usr/local/bin

    - name: Scan SBOM for vulnerabilities
      id: grype-scan
      run: |
        echo "๐Ÿ” Scanning SBOM with Grype..."

        # Scan production SBOM
        grype sbom:sbom-production.json \
          --output json \
          --file grype-results.json

        grype sbom:sbom-production.json \
          --output table \
          --file grype-results.txt

        # Count vulnerabilities by severity
        CRITICAL=$(jq '[.matches[] | select(.vulnerability.severity == "Critical")] | length' grype-results.json)
        HIGH=$(jq '[.matches[] | select(.vulnerability.severity == "High")] | length' grype-results.json)
        MEDIUM=$(jq '[.matches[] | select(.vulnerability.severity == "Medium")] | length' grype-results.json)
        LOW=$(jq '[.matches[] | select(.vulnerability.severity == "Low")] | length' grype-results.json)

        echo "critical-count=$CRITICAL" >> $GITHUB_OUTPUT
        echo "high-count=$HIGH" >> $GITHUB_OUTPUT
        echo "medium-count=$MEDIUM" >> $GITHUB_OUTPUT
        echo "low-count=$LOW" >> $GITHUB_OUTPUT

        echo "๐Ÿ” Vulnerability Summary:"
        echo "  Critical: $CRITICAL"
        echo "  High: $HIGH" 
        echo "  Medium: $MEDIUM"
        echo "  Low: $LOW"

        # Security gate
        if [[ "$CRITICAL" -gt 0 || "$HIGH" -gt 5 ]]; then
          echo "security-gate=failed" >> $GITHUB_OUTPUT
          echo "โŒ Security gate failed: Too many high-severity vulnerabilities"
        else
          echo "security-gate=passed" >> $GITHUB_OUTPUT
          echo "โœ… Security gate passed"
        fi

    - name: Generate security report
      run: |
        cat > security-report.md << EOF
        # PHP Security Analysis Report

        **Repository**: ${{ github.repository }}
        **Scan Date**: $(date -u +'%Y-%m-%d %H:%M:%S UTC')
        **Components Analyzed**: ${{ needs.sbom-generation.outputs.component-count }}

        ## Composer Audit Results
        - **Status**: ${{ steps.composer-audit.outputs.audit-status }}
        - **Vulnerabilities**: ${{ steps.composer-audit.outputs.vulnerability-count }}

        ## Grype SBOM Scan Results
        - **Critical**: ${{ steps.grype-scan.outputs.critical-count }}
        - **High**: ${{ steps.grype-scan.outputs.high-count }}
        - **Medium**: ${{ steps.grype-scan.outputs.medium-count }}
        - **Low**: ${{ steps.grype-scan.outputs.low-count }}

        ## Security Gate
        **Status**: ${{ steps.grype-scan.outputs.security-gate }}

        EOF

        if [[ "${{ steps.grype-scan.outputs.security-gate }}" == "failed" ]]; then
          cat >> security-report.md << EOF

        ### โš ๏ธ Action Required
        Critical or high-severity vulnerabilities detected. Please review and update affected packages.

        EOF
        fi

    - name: Comment on PR
      if: github.event_name == 'pull_request'
      uses: actions/github-script@v7
      with:
        script: |
          const fs = require('fs');

          let comment = `## ๐Ÿ” PHP SBOM Security Analysis\n\n`;

          // Add SBOM summary
          if (fs.existsSync('sbom-summary.md')) {
            const summary = fs.readFileSync('sbom-summary.md', 'utf8');
            comment += summary + '\n\n';
          }

          // Add security results
          if (fs.existsSync('security-report.md')) {
            const security = fs.readFileSync('security-report.md', 'utf8');
            comment += security;
          }

          comment += '\n\n๐Ÿ“„ Detailed reports available in job artifacts.';

          github.rest.issues.createComment({
            issue_number: context.issue.number,
            owner: context.repo.owner,
            repo: context.repo.repo,
            body: comment
          });

    - name: Upload security reports
      uses: actions/upload-artifact@v4
      with:
        name: security-reports
        path: |
          composer-audit.json
          grype-results.*
          security-report.md

    - name: Security gate enforcement
      if: steps.grype-scan.outputs.security-gate == 'failed'
      run: |
        echo "โŒ Security gate failed!"
        echo "Critical: ${{ steps.grype-scan.outputs.critical-count }}"
        echo "High: ${{ steps.grype-scan.outputs.high-count }}"
        echo "Please review and address security vulnerabilities before merging."
        exit 1

  deploy-sbom:
    runs-on: ubuntu-latest
    needs: [sbom-generation, security-analysis]
    if: github.ref == 'refs/heads/main' && success()

    steps:
    - name: Download SBOM artifacts
      uses: actions/download-artifact@v4
      with:
        name: php-sboms
        path: ./

    - name: Sign SBOM
      if: secrets.GPG_PRIVATE_KEY != ''
      run: |
        echo "${{ secrets.GPG_PRIVATE_KEY }}" | gpg --batch --import
        gpg --armor --detach-sign sbom-production.json
        echo "โœ… SBOM signed"

    - name: Upload to SBOM registry
      if: secrets.SBOM_REGISTRY_URL != ''
      run: |
        curl -X POST \
          -H "Authorization: Bearer ${{ secrets.SBOM_REGISTRY_TOKEN }}" \
          -H "Content-Type: application/json" \
          --data-binary @sbom-production.json \
          "${{ secrets.SBOM_REGISTRY_URL }}/api/v1/sboms/${{ github.repository }}/production/${{ github.sha }}"

        echo "โœ… SBOM uploaded to registry"

GitLab CI/CD for PHP

GitLab Pipeline Configuration
# .gitlab-ci.yml
stages:
  - build
  - sbom
  - security
  - deploy

variables:
  PHP_VERSION: "8.2"
  COMPOSER_CACHE_DIR: "$CI_PROJECT_DIR/.composer-cache"

cache:
  key: composer-cache
  paths:
    - .composer-cache/

build:
  stage: build
  image: php:${PHP_VERSION}-cli

  before_script:
    - apt-get update && apt-get install -y git unzip
    - curl -sS https://getcomposer.org/installer | php
    - mv composer.phar /usr/local/bin/composer

  script:
    - composer install --prefer-dist --no-interaction --optimize-autoloader --no-scripts

  artifacts:
    paths:
      - vendor/
    expire_in: 1 hour

generate-sbom:
  stage: sbom
  image: php:${PHP_VERSION}-cli
  dependencies:
    - build

  before_script:
    - apt-get update && apt-get install -y git unzip jq
    - curl -sS https://getcomposer.org/installer | php
    - mv composer.phar /usr/local/bin/composer
    - composer global require cyclonedx/cyclonedx-php-composer
    - export PATH="$PATH:$HOME/.composer/vendor/bin"

  script:
    # Generate production SBOM
    - |
      composer CycloneDX:make-sbom \
        --output-format=JSON \
        --output-file=sbom-production.json \
        --omit=dev

    # Generate development SBOM
    - |
      composer CycloneDX:make-sbom \
        --output-format=JSON \
        --output-file=sbom-development.json

    # Validate SBOMs
    - |
      for sbom in sbom-*.json; do
        echo "Validating $sbom"
        jq empty "$sbom" || exit 1

        component_count=$(jq '.components | length' "$sbom")
        echo "$sbom: $component_count components"
      done

    # Generate summary
    - |
      echo "SBOM Summary:" > sbom-summary.txt
      echo "=============" >> sbom-summary.txt
      echo "Production components: $(jq '.components | length' sbom-production.json)" >> sbom-summary.txt
      echo "Development components: $(jq '.components | length' sbom-development.json)" >> sbom-summary.txt

  artifacts:
    paths:
      - sbom-*.json
      - sbom-summary.txt
    reports:
      cyclonedx: sbom-production.json
    expire_in: 1 week

security-audit:
  stage: security
  image: php:${PHP_VERSION}-cli
  dependencies:
    - build
    - generate-sbom

  before_script:
    - apt-get update && apt-get install -y git unzip curl jq
    - curl -sS https://getcomposer.org/installer | php
    - mv composer.phar /usr/local/bin/composer
    - curl -sSfL https://raw.githubusercontent.com/anchore/grype/main/install.sh | sh -s -- -b /usr/local/bin

  script:
    # Composer audit
    - composer audit --format=json > composer-audit.json || true

    # Grype SBOM scan
    - grype sbom:sbom-production.json --output json --file grype-results.json
    - grype sbom:sbom-production.json --output table > grype-results.txt

    # Count vulnerabilities
    - |
      CRITICAL=$(jq '[.matches[] | select(.vulnerability.severity == "Critical")] | length' grype-results.json)
      HIGH=$(jq '[.matches[] | select(.vulnerability.severity == "High")] | length' grype-results.json)

      echo "Critical vulnerabilities: $CRITICAL"
      echo "High vulnerabilities: $HIGH"

      if [[ "$CRITICAL" -gt 0 || "$HIGH" -gt 5 ]]; then
        echo "Security gate failed: Too many critical/high vulnerabilities"
        exit 1
      fi

  artifacts:
    paths:
      - composer-audit.json
      - grype-results.*
    expire_in: 1 week

  allow_failure: false

deploy-sbom:
  stage: deploy
  image: alpine:latest
  dependencies:
    - generate-sbom

  before_script:
    - apk add --no-cache curl jq

  script:
    - |
      if [[ -n "$SBOM_REGISTRY_URL" && -n "$SBOM_REGISTRY_TOKEN" ]]; then
        echo "Uploading SBOM to registry..."

        curl -X POST \
          -H "Authorization: Bearer $SBOM_REGISTRY_TOKEN" \
          -H "Content-Type: application/json" \
          --data-binary @sbom-production.json \
          "$SBOM_REGISTRY_URL/api/v1/sboms/$CI_PROJECT_NAME/$CI_COMMIT_SHA"

        echo "SBOM uploaded successfully"
      else
        echo "SBOM registry not configured, skipping upload"
      fi

  only:
    - main

๐Ÿ”ง Advanced Security and Compliance

Custom Vulnerability Analysis

Enhanced Security Scanner
<?php
// src/Security/PhpSbomSecurityAnalyzer.php

namespace App\Security;

use Symfony\Component\Process\Exception\ProcessFailedException;
use Symfony\Component\Process\Process;

class PhpSbomSecurityAnalyzer
{
    private array $config;
    private array $vulnerabilitySources;

    public function __construct(array $config = [])
    {
        $this->config = array_merge([
            'composer_audit' => true,
            'grype_scan' => true,
            'snyk_scan' => false,
            'custom_rules' => [],
        ], $config);

        $this->vulnerabilitySources = [];
    }

    public function analyzeSbom(string $sbomPath): SecurityAnalysisResult
    {
        $result = new SecurityAnalysisResult();

        // Load SBOM
        $sbom = $this->loadSbom($sbomPath);
        $result->setComponentCount(count($sbom['components'] ?? []));

        // Run Composer audit if available
        if ($this->config['composer_audit']) {
            $this->runComposerAudit($result);
        }

        // Run Grype scan
        if ($this->config['grype_scan']) {
            $this->runGrypeScan($sbomPath, $result);
        }

        // Run Snyk scan if configured
        if ($this->config['snyk_scan']) {
            $this->runSnykScan($sbomPath, $result);
        }

        // Apply custom security rules
        $this->applyCustomRules($sbom, $result);

        // Calculate risk score
        $result->calculateRiskScore();

        return $result;
    }

    private function runComposerAudit(SecurityAnalysisResult $result): void
    {
        try {
            $process = new Process(['composer', 'audit', '--format=json']);
            $process->run();

            if ($process->isSuccessful()) {
                $auditData = json_decode($process->getOutput(), true);

                foreach ($auditData['advisories'] ?? [] as $advisory) {
                    $vulnerability = new Vulnerability([
                        'source' => 'composer',
                        'package' => $advisory['packageName'],
                        'severity' => $this->mapComposerSeverity($advisory['severity'] ?? 'medium'),
                        'cve' => $advisory['cve'] ?? null,
                        'title' => $advisory['title'] ?? 'Unknown vulnerability',
                        'description' => $advisory['description'] ?? '',
                        'affected_versions' => $advisory['affectedVersions'] ?? '',
                        'fixed_versions' => $advisory['sources'][0]['remoteId'] ?? null,
                    ]);

                    $result->addVulnerability($vulnerability);
                }
            }
        } catch (ProcessFailedException $e) {
            // Composer audit failed - may indicate vulnerabilities
            $result->addError('composer_audit_failed', $e->getMessage());
        }
    }

    private function runGrypeScan(string $sbomPath, SecurityAnalysisResult $result): void
    {
        try {
            $process = new Process([
                'grype', 
                "sbom:$sbomPath", 
                '--output', 'json'
            ]);
            $process->run();

            if ($process->isSuccessful()) {
                $grypeData = json_decode($process->getOutput(), true);

                foreach ($grypeData['matches'] ?? [] as $match) {
                    $vulnerability = new Vulnerability([
                        'source' => 'grype',
                        'package' => $match['artifact']['name'],
                        'version' => $match['artifact']['version'],
                        'severity' => strtolower($match['vulnerability']['severity'] ?? 'unknown'),
                        'cve' => $match['vulnerability']['id'],
                        'description' => $match['vulnerability']['description'] ?? '',
                        'fixed_version' => $match['vulnerability']['fix']['versions'][0] ?? null,
                        'data_source' => $match['vulnerability']['dataSource'] ?? 'unknown',
                    ]);

                    $result->addVulnerability($vulnerability);
                }
            }
        } catch (ProcessFailedException $e) {
            $result->addError('grype_scan_failed', $e->getMessage());
        }
    }

    private function applyCustomRules(array $sbom, SecurityAnalysisResult $result): void
    {
        foreach ($this->config['custom_rules'] as $rule) {
            $this->evaluateCustomRule($rule, $sbom, $result);
        }
    }

    private function evaluateCustomRule(array $rule, array $sbom, SecurityAnalysisResult $result): void
    {
        $ruleType = $rule['type'];
        $condition = $rule['condition'];

        switch ($ruleType) {
            case 'package_blacklist':
                $this->checkPackageBlacklist($condition, $sbom, $result);
                break;

            case 'license_policy':
                $this->checkLicensePolicy($condition, $sbom, $result);
                break;

            case 'version_policy':
                $this->checkVersionPolicy($condition, $sbom, $result);
                break;

            case 'age_policy':
                $this->checkPackageAge($condition, $sbom, $result);
                break;
        }
    }

    private function checkPackageBlacklist(array $blacklist, array $sbom, SecurityAnalysisResult $result): void
    {
        foreach ($sbom['components'] ?? [] as $component) {
            $packageName = $component['name'] ?? '';

            if (in_array($packageName, $blacklist)) {
                $violation = new PolicyViolation([
                    'type' => 'blacklisted_package',
                    'severity' => 'high',
                    'package' => $packageName,
                    'message' => "Package '$packageName' is blacklisted by security policy",
                    'recommendation' => 'Remove this package or get security approval',
                ]);

                $result->addPolicyViolation($violation);
            }
        }
    }

    private function checkLicensePolicy(array $policy, array $sbom, SecurityAnalysisResult $result): void
    {
        $forbiddenLicenses = $policy['forbidden'] ?? [];
        $requireApprovalLicenses = $policy['require_approval'] ?? [];

        foreach ($sbom['components'] ?? [] as $component) {
            $licenses = $component['licenses'] ?? [];
            $packageName = $component['name'] ?? 'unknown';

            foreach ($licenses as $license) {
                $licenseName = $license['license']['name'] ?? $license['license']['id'] ?? 'unknown';

                if (in_array($licenseName, $forbiddenLicenses)) {
                    $violation = new PolicyViolation([
                        'type' => 'forbidden_license',
                        'severity' => 'critical',
                        'package' => $packageName,
                        'license' => $licenseName,
                        'message' => "Package '$packageName' uses forbidden license '$licenseName'",
                        'recommendation' => 'Replace with compatible alternative or get legal approval',
                    ]);

                    $result->addPolicyViolation($violation);
                }

                if (in_array($licenseName, $requireApprovalLicenses)) {
                    $violation = new PolicyViolation([
                        'type' => 'license_requires_approval',
                        'severity' => 'medium',
                        'package' => $packageName,
                        'license' => $licenseName,
                        'message' => "Package '$packageName' uses license '$licenseName' that requires approval",
                        'recommendation' => 'Get legal team approval before production use',
                    ]);

                    $result->addPolicyViolation($violation);
                }
            }
        }
    }

    private function loadSbom(string $path): array
    {
        $content = file_get_contents($path);
        return json_decode($content, true) ?? [];
    }

    private function mapComposerSeverity(string $severity): string
    {
        $mapping = [
            'low' => 'low',
            'medium' => 'medium', 
            'high' => 'high',
            'critical' => 'critical',
        ];

        return $mapping[strtolower($severity)] ?? 'unknown';
    }
}

class SecurityAnalysisResult
{
    private int $componentCount = 0;
    private array $vulnerabilities = [];
    private array $policyViolations = [];
    private array $errors = [];
    private ?float $riskScore = null;

    public function addVulnerability(Vulnerability $vulnerability): void
    {
        $this->vulnerabilities[] = $vulnerability;
    }

    public function addPolicyViolation(PolicyViolation $violation): void
    {
        $this->policyViolations[] = $violation;
    }

    public function addError(string $type, string $message): void
    {
        $this->errors[] = ['type' => $type, 'message' => $message];
    }

    public function calculateRiskScore(): void
    {
        $score = 0;

        // Base score from vulnerabilities
        foreach ($this->vulnerabilities as $vuln) {
            $score += match($vuln->getSeverity()) {
                'critical' => 10,
                'high' => 7,
                'medium' => 4,
                'low' => 1,
                default => 0,
            };
        }

        // Policy violations add to risk
        foreach ($this->policyViolations as $violation) {
            $score += match($violation->getSeverity()) {
                'critical' => 8,
                'high' => 5,
                'medium' => 3,
                'low' => 1,
                default => 0,
            };
        }

        // Normalize to 0-100 scale
        $this->riskScore = min(100, $score);
    }

    // Getters...
    public function getVulnerabilities(): array { return $this->vulnerabilities; }
    public function getPolicyViolations(): array { return $this->policyViolations; }
    public function getErrors(): array { return $this->errors; }
    public function getRiskScore(): ?float { return $this->riskScore; }
    public function getComponentCount(): int { return $this->componentCount; }
    public function setComponentCount(int $count): void { $this->componentCount = $count; }
}

class Vulnerability
{
    private array $data;

    public function __construct(array $data)
    {
        $this->data = $data;
    }

    public function getSeverity(): string
    {
        return $this->data['severity'] ?? 'unknown';
    }

    public function getPackage(): string
    {
        return $this->data['package'] ?? 'unknown';
    }

    public function getCve(): ?string
    {
        return $this->data['cve'] ?? null;
    }

    public function getDescription(): string
    {
        return $this->data['description'] ?? '';
    }

    public function toArray(): array
    {
        return $this->data;
    }
}

class PolicyViolation
{
    private array $data;

    public function __construct(array $data)
    {
        $this->data = $data;
    }

    public function getSeverity(): string
    {
        return $this->data['severity'] ?? 'unknown';
    }

    public function getType(): string
    {
        return $this->data['type'] ?? 'unknown';
    }

    public function getMessage(): string
    {
        return $this->data['message'] ?? '';
    }

    public function toArray(): array
    {
        return $this->data;
    }
}
Security Configuration
<?php
// config/security.php

return [
    'sbom' => [
        'security_analysis' => [
            'composer_audit' => env('SBOM_COMPOSER_AUDIT', true),
            'grype_scan' => env('SBOM_GRYPE_SCAN', true),
            'snyk_scan' => env('SBOM_SNYK_SCAN', false),

            'custom_rules' => [
                [
                    'type' => 'package_blacklist',
                    'condition' => [
                        'vendor/suspicious-package',
                        'company/deprecated-lib',
                    ],
                ],
                [
                    'type' => 'license_policy',
                    'condition' => [
                        'forbidden' => ['GPL-3.0', 'AGPL-3.0'],
                        'require_approval' => ['GPL-2.0', 'LGPL-3.0'],
                    ],
                ],
                [
                    'type' => 'version_policy',
                    'condition' => [
                        'min_php_version' => '8.1',
                        'max_package_age_days' => 365,
                    ],
                ],
            ],
        ],

        'risk_thresholds' => [
            'low' => 20,
            'medium' => 50,
            'high' => 80,
            'critical' => 100,
        ],

        'reporting' => [
            'slack_webhook' => env('SECURITY_SLACK_WEBHOOK'),
            'email_recipients' => env('SECURITY_EMAIL_RECIPIENTS'),
            'jira_integration' => env('SECURITY_JIRA_ENABLED', false),
        ],
    ],
];

License Compliance Framework

License Policy Engine
<?php
// src/Compliance/LicenseComplianceChecker.php

namespace App\Compliance;

class LicenseComplianceChecker
{
    private array $policy;
    private array $licenseDb;

    public function __construct(array $policy)
    {
        $this->policy = $policy;
        $this->loadLicenseDatabase();
    }

    public function checkCompliance(string $sbomPath): ComplianceResult
    {
        $sbom = json_decode(file_get_contents($sbomPath), true);
        $result = new ComplianceResult();

        foreach ($sbom['components'] ?? [] as $component) {
            $this->checkComponentLicenses($component, $result);
        }

        $result->calculateComplianceScore();

        return $result;
    }

    private function checkComponentLicenses(array $component, ComplianceResult $result): void
    {
        $packageName = $component['name'] ?? 'unknown';
        $licenses = $component['licenses'] ?? [];

        if (empty($licenses)) {
            $result->addIssue(new ComplianceIssue([
                'type' => 'missing_license',
                'severity' => 'medium',
                'package' => $packageName,
                'message' => "Package '$packageName' has no license information",
                'recommendation' => 'Investigate license or contact package maintainer',
            ]));
            return;
        }

        foreach ($licenses as $license) {
            $licenseName = $license['license']['name'] ?? $license['license']['id'] ?? 'unknown';
            $this->evaluateLicense($licenseName, $packageName, $result);
        }
    }

    private function evaluateLicense(string $licenseName, string $packageName, ComplianceResult $result): void
    {
        // Check against policy
        if (in_array($licenseName, $this->policy['forbidden'])) {
            $result->addIssue(new ComplianceIssue([
                'type' => 'forbidden_license',
                'severity' => 'critical',
                'package' => $packageName,
                'license' => $licenseName,
                'message' => "Package '$packageName' uses forbidden license '$licenseName'",
                'recommendation' => 'Remove package or seek legal exception',
            ]));
        }

        if (in_array($licenseName, $this->policy['requires_attribution'])) {
            $result->addRequirement(new AttributionRequirement([
                'package' => $packageName,
                'license' => $licenseName,
                'attribution_text' => $this->getAttributionText($licenseName),
            ]));
        }

        if (in_array($licenseName, $this->policy['copyleft'])) {
            $result->addRequirement(new CopyleftRequirement([
                'package' => $packageName,
                'license' => $licenseName,
                'source_code_requirement' => $this->getCopyleftRequirements($licenseName),
            ]));
        }
    }

    private function loadLicenseDatabase(): void
    {
        // Load SPDX license database or custom license definitions
        $this->licenseDb = [
            'MIT' => [
                'type' => 'permissive',
                'requires_attribution' => true,
                'allows_modification' => true,
                'allows_distribution' => true,
                'allows_private_use' => true,
                'allows_commercial_use' => true,
            ],
            'GPL-3.0' => [
                'type' => 'copyleft',
                'requires_attribution' => true,
                'requires_source_code' => true,
                'allows_modification' => true,
                'allows_distribution' => true,
                'allows_private_use' => true,
                'allows_commercial_use' => true,
            ],
            // ... more licenses
        ];
    }

    private function getAttributionText(string $licenseName): string
    {
        return match($licenseName) {
            'MIT' => 'Copyright notice and license text must be included',
            'Apache-2.0' => 'Copyright notice, license text, and NOTICE file must be included',
            default => 'Attribution requirements vary - consult license text',
        };
    }

    private function getCopyleftRequirements(string $licenseName): string
    {
        return match($licenseName) {
            'GPL-2.0', 'GPL-3.0' => 'Source code must be made available under same license',
            'LGPL-2.1', 'LGPL-3.0' => 'Source code of library must be made available',
            'MPL-2.0' => 'Modified files must be made available under MPL-2.0',
            default => 'Consult license text for specific requirements',
        };
    }
}

class ComplianceResult
{
    private array $issues = [];
    private array $requirements = [];
    private ?float $complianceScore = null;

    public function addIssue(ComplianceIssue $issue): void
    {
        $this->issues[] = $issue;
    }

    public function addRequirement($requirement): void
    {
        $this->requirements[] = $requirement;
    }

    public function calculateComplianceScore(): void
    {
        $totalComponents = 100; // Assume 100 for scoring
        $criticalIssues = count(array_filter($this->issues, fn($i) => $i->getSeverity() === 'critical'));
        $highIssues = count(array_filter($this->issues, fn($i) => $i->getSeverity() === 'high'));
        $mediumIssues = count(array_filter($this->issues, fn($i) => $i->getSeverity() === 'medium'));

        // Calculate deductions
        $deductions = ($criticalIssues * 20) + ($highIssues * 10) + ($mediumIssues * 5);

        $this->complianceScore = max(0, 100 - $deductions);
    }

    // Getters...
    public function getIssues(): array { return $this->issues; }
    public function getRequirements(): array { return $this->requirements; }
    public function getComplianceScore(): ?float { return $this->complianceScore; }

    public function generateReport(): array
    {
        return [
            'compliance_score' => $this->complianceScore,
            'total_issues' => count($this->issues),
            'critical_issues' => count(array_filter($this->issues, fn($i) => $i->getSeverity() === 'critical')),
            'high_issues' => count(array_filter($this->issues, fn($i) => $i->getSeverity() === 'high')),
            'medium_issues' => count(array_filter($this->issues, fn($i) => $i->getSeverity() === 'medium')),
            'attribution_requirements' => count(array_filter($this->requirements, fn($r) => $r instanceof AttributionRequirement)),
            'copyleft_requirements' => count(array_filter($this->requirements, fn($r) => $r instanceof CopyleftRequirement)),
            'issues' => array_map(fn($i) => $i->toArray(), $this->issues),
            'requirements' => array_map(fn($r) => $r->toArray(), $this->requirements),
        ];
    }
}

class ComplianceIssue
{
    private array $data;

    public function __construct(array $data)
    {
        $this->data = $data;
    }

    public function getSeverity(): string
    {
        return $this->data['severity'] ?? 'unknown';
    }

    public function toArray(): array
    {
        return $this->data;
    }
}

class AttributionRequirement
{
    private array $data;

    public function __construct(array $data)
    {
        $this->data = $data;
    }

    public function toArray(): array
    {
        return $this->data;
    }
}

class CopyleftRequirement
{
    private array $data;

    public function __construct(array $data)
    {
        $this->data = $data;
    }

    public function toArray(): array
    {
        return $this->data;
    }
}

๐Ÿ“ˆ Performance and Scalability

Large Project Optimization

Parallel SBOM Generation
#!/bin/bash
# parallel-php-sbom.sh - High-performance SBOM generation

PROJECTS_DIR="$1"
OUTPUT_DIR="${2:-sboms}"
MAX_PARALLEL="${3:-4}"

if [ -z "$PROJECTS_DIR" ]; then
    echo "Usage: $0 <projects-dir> [output-dir] [max-parallel]"
    exit 1
fi

echo "๐Ÿš€ Parallel PHP SBOM Generation"
echo "==============================="
echo "Projects directory: $PROJECTS_DIR"
echo "Output directory: $OUTPUT_DIR"
echo "Max parallel jobs: $MAX_PARALLEL"

# Create output directory
mkdir -p "$OUTPUT_DIR"

# Function to generate SBOM for a single project
generate_project_sbom() {
    local project_path="$1"
    local project_name=$(basename "$project_path")
    local output_file="$OUTPUT_DIR/${project_name}-sbom.json"

    echo "๐Ÿ“ฆ Processing: $project_name"

    # Skip if no composer.json
    if [ ! -f "$project_path/composer.json" ]; then
        echo "  โš ๏ธ  No composer.json found, skipping"
        return 0
    fi

    # Skip if no composer.lock
    if [ ! -f "$project_path/composer.lock" ]; then
        echo "  โš ๏ธ  No composer.lock found, running composer install..."
        cd "$project_path"
        composer install --no-scripts --no-plugins --quiet
        cd - > /dev/null
    fi

    # Generate SBOM with timeout
    cd "$project_path"

    timeout 300s composer CycloneDX:make-sbom \
        --output-format=JSON \
        --output-file="$output_file" \
        > /dev/null 2>&1

    local exit_code=$?
    cd - > /dev/null

    if [ $exit_code -eq 0 ] && [ -f "$output_file" ]; then
        # Validate generated SBOM
        if jq empty "$output_file" 2>/dev/null; then
            local component_count=$(jq '.components | length' "$output_file" 2>/dev/null || echo "0")
            echo "  โœ… $project_name: $component_count components"
        else
            echo "  โŒ $project_name: Invalid JSON generated"
            rm -f "$output_file"
        fi
    else
        echo "  โŒ $project_name: Generation failed or timed out"
    fi
}

# Export function for parallel execution
export -f generate_project_sbom
export OUTPUT_DIR

# Find all PHP projects
mapfile -t projects < <(find "$PROJECTS_DIR" -name "composer.json" -type f -exec dirname {} \; | sort -u)

echo "Found ${#projects[@]} PHP projects"

# Process projects in parallel
printf '%s\n' "${projects[@]}" | xargs -n 1 -P "$MAX_PARALLEL" -I {} bash -c 'generate_project_sbom "$@"' _ {}

# Generate consolidated report
echo ""
echo "๐Ÿ“Š Generation Summary:"
echo "====================="

total_sboms=$(ls -1 "$OUTPUT_DIR"/*.json 2>/dev/null | wc -l)
echo "Generated SBOMs: $total_sboms"

if [ "$total_sboms" -gt 0 ]; then
    total_components=$(find "$OUTPUT_DIR" -name "*.json" -exec jq -r '.components | length' {} \; | awk '{sum+=$1} END {print sum}')
    echo "Total components: $total_components"

    # License distribution across all projects
    echo ""
    echo "๐Ÿ“„ License Distribution:"
    find "$OUTPUT_DIR" -name "*.json" -exec jq -r '.components[]?.licenses[]?.license.name // .components[]?.licenses[]?.license.id // "Unknown"' {} \; | \
        sort | uniq -c | sort -nr | head -10 | \
        while read count license; do
            echo "  $license: $count packages"
        done
fi

echo ""
echo "๐ŸŽ‰ Parallel SBOM generation completed!"
Memory-Efficient Processing
<?php
// src/Performance/MemoryEfficientSbomProcessor.php

namespace App\Performance;

class MemoryEfficientSbomProcessor
{
    private int $batchSize;
    private string $tempDir;

    public function __construct(int $batchSize = 100)
    {
        $this->batchSize = $batchSize;
        $this->tempDir = sys_get_temp_dir() . '/sbom_processing';

        if (!is_dir($this->tempDir)) {
            mkdir($this->tempDir, 0755, true);
        }
    }

    public function processLargeSbom(string $sbomPath): ProcessingResult
    {
        $result = new ProcessingResult();

        // Stream process the SBOM file
        $handle = fopen($sbomPath, 'r');
        if (!$handle) {
            throw new \RuntimeException("Cannot open SBOM file: $sbomPath");
        }

        $result->startTiming();

        try {
            // Parse SBOM structure
            $sbomData = $this->parseJsonStream($handle);

            // Process components in batches
            $components = $sbomData['components'] ?? [];
            $totalComponents = count($components);

            $result->setTotalComponents($totalComponents);

            for ($offset = 0; $offset < $totalComponents; $offset += $this->batchSize) {
                $batch = array_slice($components, $offset, $this->batchSize);
                $this->processBatch($batch, $offset, $result);

                // Clear memory
                unset($batch);

                if (function_exists('gc_collect_cycles')) {
                    gc_collect_cycles();
                }
            }

        } finally {
            fclose($handle);
            $result->endTiming();
        }

        return $result;
    }

    private function parseJsonStream($handle): array
    {
        // For very large files, we might need to use a streaming JSON parser
        // For now, we'll use a simple approach
        $content = stream_get_contents($handle);
        return json_decode($content, true) ?? [];
    }

    private function processBatch(array $components, int $offset, ProcessingResult $result): void
    {
        foreach ($components as $index => $component) {
            $globalIndex = $offset + $index;

            // Process component
            $this->processComponent($component, $globalIndex, $result);

            // Memory usage check
            $memoryUsage = memory_get_usage(true);
            $result->updatePeakMemory($memoryUsage);

            if ($memoryUsage > $this->getMemoryLimit() * 0.8) {
                // Approaching memory limit - force garbage collection
                if (function_exists('gc_collect_cycles')) {
                    gc_collect_cycles();
                }

                // If still high, write to temp file and continue
                if (memory_get_usage(true) > $this->getMemoryLimit() * 0.8) {
                    $this->writeIntermediateResults($result, $globalIndex);
                    $result->clearAccumulatedData();
                }
            }
        }
    }

    private function processComponent(array $component, int $index, ProcessingResult $result): void
    {
        // Extract relevant information
        $packageName = $component['name'] ?? 'unknown';
        $version = $component['version'] ?? 'unknown';
        $licenses = $component['licenses'] ?? [];

        // Update statistics
        $result->incrementProcessedComponents();

        // License tracking
        foreach ($licenses as $license) {
            $licenseName = $license['license']['name'] ?? $license['license']['id'] ?? 'unknown';
            $result->addLicense($licenseName);
        }

        // Vulnerability context if available
        if (isset($component['vulnerabilities'])) {
            $result->addVulnerableComponent($packageName, count($component['vulnerabilities']));
        }

        // Package source analysis
        $purl = $component['purl'] ?? '';
        if (str_contains($purl, 'composer')) {
            $result->addComposerPackage();
        }
    }

    private function getMemoryLimit(): int
    {
        $limit = ini_get('memory_limit');

        if ($limit === '-1') {
            return PHP_INT_MAX;
        }

        $value = (int) $limit;
        $unit = strtoupper(substr($limit, -1));

        return match($unit) {
            'G' => $value * 1024 * 1024 * 1024,
            'M' => $value * 1024 * 1024,
            'K' => $value * 1024,
            default => $value,
        };
    }

    private function writeIntermediateResults(ProcessingResult $result, int $currentIndex): void
    {
        $filename = $this->tempDir . "/intermediate_results_{$currentIndex}.json";
        file_put_contents($filename, json_encode($result->getSerializableData()));

        $result->addIntermediateFile($filename);
    }

    public function __destruct()
    {
        // Clean up temp files
        if (is_dir($this->tempDir)) {
            $files = glob($this->tempDir . '/*');
            foreach ($files as $file) {
                unlink($file);
            }
            rmdir($this->tempDir);
        }
    }
}

class ProcessingResult
{
    private int $totalComponents = 0;
    private int $processedComponents = 0;
    private array $licenseDistribution = [];
    private array $vulnerableComponents = [];
    private int $composerPackages = 0;
    private int $peakMemoryUsage = 0;
    private float $startTime = 0;
    private float $endTime = 0;
    private array $intermediateFiles = [];

    public function startTiming(): void
    {
        $this->startTime = microtime(true);
    }

    public function endTiming(): void
    {
        $this->endTime = microtime(true);
    }

    public function getProcessingTime(): float
    {
        return $this->endTime - $this->startTime;
    }

    public function setTotalComponents(int $total): void
    {
        $this->totalComponents = $total;
    }

    public function incrementProcessedComponents(): void
    {
        $this->processedComponents++;
    }

    public function addLicense(string $license): void
    {
        $this->licenseDistribution[$license] = ($this->licenseDistribution[$license] ?? 0) + 1;
    }

    public function addVulnerableComponent(string $package, int $vulnCount): void
    {
        $this->vulnerableComponents[$package] = $vulnCount;
    }

    public function addComposerPackage(): void
    {
        $this->composerPackages++;
    }

    public function updatePeakMemory(int $usage): void
    {
        $this->peakMemoryUsage = max($this->peakMemoryUsage, $usage);
    }

    public function addIntermediateFile(string $filename): void
    {
        $this->intermediateFiles[] = $filename;
    }

    public function clearAccumulatedData(): void
    {
        // Keep only essential counters, clear large arrays
        $this->licenseDistribution = [];
        $this->vulnerableComponents = [];
    }

    public function getSerializableData(): array
    {
        return [
            'processed_components' => $this->processedComponents,
            'license_distribution' => $this->licenseDistribution,
            'vulnerable_components' => $this->vulnerableComponents,
            'composer_packages' => $this->composerPackages,
        ];
    }

    public function generateReport(): array
    {
        // Merge data from intermediate files if any
        foreach ($this->intermediateFiles as $file) {
            if (file_exists($file)) {
                $data = json_decode(file_get_contents($file), true);
                $this->mergeIntermediateData($data);
            }
        }

        return [
            'summary' => [
                'total_components' => $this->totalComponents,
                'processed_components' => $this->processedComponents,
                'composer_packages' => $this->composerPackages,
                'unique_licenses' => count($this->licenseDistribution),
                'vulnerable_components' => count($this->vulnerableComponents),
                'processing_time_seconds' => round($this->getProcessingTime(), 2),
                'peak_memory_usage_mb' => round($this->peakMemoryUsage / (1024 * 1024), 2),
            ],
            'license_distribution' => $this->licenseDistribution,
            'vulnerable_components' => $this->vulnerableComponents,
        ];
    }

    private function mergeIntermediateData(array $data): void
    {
        // Merge license distributions
        foreach ($data['license_distribution'] ?? [] as $license => $count) {
            $this->licenseDistribution[$license] = ($this->licenseDistribution[$license] ?? 0) + $count;
        }

        // Merge vulnerable components
        foreach ($data['vulnerable_components'] ?? [] as $package => $vulnCount) {
            $this->vulnerableComponents[$package] = $vulnCount;
        }

        $this->composerPackages += $data['composer_packages'] ?? 0;
    }
}

PHP 8.3+ Modern Features

Attribute-Based SBOM Metadata
<?php
// Modern PHP with attributes for SBOM metadata

#[Package(
    name: "company/advanced-lib",
    version: "2.1.0",
    type: "library",
    security: [
        "cve-scanning" => true,
        "license-check" => true,
        "supply-chain-verification" => true
    ]
)]
class AdvancedLibrary
{
    #[Dependency(
        package: "guzzlehttp/guzzle",
        version: "^7.5",
        scope: "production",
        security_critical: true
    )]
    private HttpClient $httpClient;

    #[Dependency(
        package: "monolog/monolog", 
        version: "^3.0",
        scope: "production",
        pii_handling: true
    )]
    private Logger $logger;
}

#[\Attribute]
class Package
{
    public function __construct(
        public string $name,
        public string $version,
        public string $type = 'library',
        public array $security = []
    ) {}
}

#[\Attribute]
class Dependency
{
    public function __construct(
        public string $package,
        public string $version,
        public string $scope = 'production',
        public bool $security_critical = false,
        public bool $pii_handling = false
    ) {}
}

Cloud-Native PHP Integration

Kubernetes SBOM Sidecar
# k8s-php-sbom-sidecar.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: php-app-with-sbom
  namespace: production
spec:
  replicas: 3
  selector:
    matchLabels:
      app: php-app
  template:
    metadata:
      labels:
        app: php-app
      annotations:
        sbom.security/enabled: "true"
        sbom.security/format: "cyclonedx"
    spec:
      containers:
      - name: php-app
        image: php-app:latest
        ports:
        - containerPort: 8080
        volumeMounts:
        - name: sbom-data
          mountPath: /var/sbom

      # SBOM Generator Sidecar
      - name: sbom-generator
        image: composer:2
        command:
        - /bin/sh
        - -c
        - |
          composer global require --no-interaction cyclonedx/cyclonedx-php-composer

          while true; do
            echo "Generating SBOM for PHP application..."

            # Extract app files
            cp -r /app/vendor /tmp/analysis/ 2>/dev/null || true
            cp /app/composer.* /tmp/analysis/ 2>/dev/null || true

            # Generate SBOM
            cd /tmp/analysis
            composer CycloneDX:make-sbom \
              --output-format=JSON \
              --output-file=/var/sbom/current-sbom.json

            # Upload to SBOM service
            curl -X POST \
              -H "Authorization: Bearer $SBOM_API_TOKEN" \
              -H "Content-Type: application/json" \
              --data-binary @/var/sbom/current-sbom.json \
              "$SBOM_API_ENDPOINT/upload"

            sleep 3600  # Generate hourly
          done
        env:
        - name: SBOM_API_TOKEN
          valueFrom:
            secretKeyRef:
              name: sbom-api-credentials
              key: token
        - name: SBOM_API_ENDPOINT
          value: "https://sbom-service.company.com/api/v1"
        volumeMounts:
        - name: sbom-data
          mountPath: /var/sbom
        - name: app-source
          mountPath: /app
          readOnly: true

      volumes:
      - name: sbom-data
        emptyDir: {}
      - name: app-source
        configMap:
          name: php-app-source

AI-Enhanced SBOM Analysis

Machine Learning for PHP Package Risk Assessment
<?php
// src/AI/PhpPackageRiskAnalyzer.php

namespace App\AI;

class PhpPackageRiskAnalyzer
{
    private MLModel $riskModel;
    private array $features;

    public function __construct()
    {
        $this->loadRiskModel();
        $this->initializeFeatures();
    }

    public function analyzePackageRisk(array $sbom): RiskAnalysisResult
    {
        $result = new RiskAnalysisResult();

        foreach ($sbom['components'] ?? [] as $component) {
            $risk = $this->calculatePackageRisk($component);
            $result->addPackageRisk($component['name'], $risk);
        }

        return $result;
    }

    private function calculatePackageRisk(array $component): PackageRisk
    {
        $features = $this->extractFeatures($component);
        $riskScore = $this->riskModel->predict($features);

        return new PackageRisk([
            'package' => $component['name'],
            'version' => $component['version'] ?? 'unknown',
            'risk_score' => $riskScore,
            'risk_factors' => $this->identifyRiskFactors($features),
            'recommendations' => $this->generateRecommendations($component, $riskScore),
        ]);
    }

    private function extractFeatures(array $component): array
    {
        return [
            // Package metadata features
            'package_age_days' => $this->getPackageAge($component),
            'version_count' => $this->getVersionCount($component['name']),
            'download_count' => $this->getDownloadCount($component['name']),
            'github_stars' => $this->getGitHubStars($component),
            'maintainer_count' => $this->getMaintainerCount($component['name']),
            'last_update_days' => $this->getLastUpdateDays($component),

            // Security features
            'has_security_policy' => $this->hasSecurityPolicy($component),
            'vulnerability_count' => $this->getHistoricalVulnerabilities($component['name']),
            'has_code_signing' => $this->hasCodeSigning($component),

            // Code quality features
            'has_tests' => $this->hasTests($component),
            'code_coverage' => $this->getCodeCoverage($component),
            'has_ci' => $this->hasContinuousIntegration($component),

            // Dependency features
            'dependency_count' => $this->getDependencyCount($component),
            'circular_dependencies' => $this->hasCircularDependencies($component),
            'deprecated_dependencies' => $this->hasDeprecatedDependencies($component),

            // License and legal
            'license_risk_score' => $this->getLicenseRiskScore($component),
            'license_compatibility' => $this->getLicenseCompatibility($component),
        ];
    }

    private function identifyRiskFactors(array $features): array
    {
        $factors = [];

        if ($features['package_age_days'] < 30) {
            $factors[] = 'Very new package (< 30 days)';
        }

        if ($features['maintainer_count'] < 2) {
            $factors[] = 'Single maintainer dependency';
        }

        if ($features['last_update_days'] > 365) {
            $factors[] = 'Not updated in over a year';
        }

        if ($features['vulnerability_count'] > 5) {
            $factors[] = 'History of multiple vulnerabilities';
        }

        if (!$features['has_tests']) {
            $factors[] = 'No test coverage detected';
        }

        if ($features['dependency_count'] > 50) {
            $factors[] = 'High number of dependencies';
        }

        return $factors;
    }

    private function generateRecommendations(array $component, float $riskScore): array
    {
        $recommendations = [];

        if ($riskScore > 0.8) {
            $recommendations[] = 'Consider finding alternative package';
            $recommendations[] = 'Implement additional monitoring';
            $recommendations[] = 'Conduct security review';
        } elseif ($riskScore > 0.6) {
            $recommendations[] = 'Monitor for updates regularly';
            $recommendations[] = 'Review security advisories';
        } elseif ($riskScore > 0.4) {
            $recommendations[] = 'Standard monitoring sufficient';
        }

        return $recommendations;
    }

    // Feature extraction methods (implementations would query various APIs)
    private function getPackageAge(array $component): int
    {
        // Query Packagist API for first release date
        return 365; // Placeholder
    }

    private function getVersionCount(string $packageName): int
    {
        // Query Packagist API for version history
        return 10; // Placeholder
    }

    private function getDownloadCount(string $packageName): int
    {
        // Query Packagist API for download statistics
        return 1000000; // Placeholder
    }

    // ... other feature extraction methods

    private function loadRiskModel(): void
    {
        // Load pre-trained ML model for risk assessment
        // This could be a TensorFlow model, scikit-learn model, etc.
        $this->riskModel = new MockMLModel();
    }

    private function initializeFeatures(): void
    {
        $this->features = [
            'package_metadata' => ['age', 'popularity', 'maintenance'],
            'security_indicators' => ['vulnerabilities', 'policies', 'signing'],
            'code_quality' => ['tests', 'coverage', 'ci'],
            'dependencies' => ['count', 'freshness', 'circular'],
            'licensing' => ['compatibility', 'risk'],
        ];
    }
}

class MockMLModel
{
    public function predict(array $features): float
    {
        // Mock implementation - in reality this would use a trained model
        $riskScore = 0.0;

        // Simple heuristic-based scoring
        if ($features['package_age_days'] < 30) $riskScore += 0.2;
        if ($features['maintainer_count'] < 2) $riskScore += 0.3;
        if ($features['vulnerability_count'] > 3) $riskScore += 0.4;
        if (!$features['has_tests']) $riskScore += 0.1;

        return min(1.0, $riskScore);
    }
}

class RiskAnalysisResult
{
    private array $packageRisks = [];

    public function addPackageRisk(string $packageName, PackageRisk $risk): void
    {
        $this->packageRisks[$packageName] = $risk;
    }

    public function getHighRiskPackages(float $threshold = 0.7): array
    {
        return array_filter($this->packageRisks, fn($risk) => $risk->getRiskScore() >= $threshold);
    }

    public function generateReport(): array
    {
        $highRisk = $this->getHighRiskPackages();
        $mediumRisk = $this->getHighRiskPackages(0.4);

        return [
            'summary' => [
                'total_packages' => count($this->packageRisks),
                'high_risk_packages' => count($highRisk),
                'medium_risk_packages' => count($mediumRisk) - count($highRisk),
                'average_risk_score' => $this->getAverageRiskScore(),
            ],
            'high_risk_packages' => array_map(fn($risk) => $risk->toArray(), $highRisk),
            'recommendations' => $this->getGlobalRecommendations(),
        ];
    }

    private function getAverageRiskScore(): float
    {
        if (empty($this->packageRisks)) {
            return 0.0;
        }

        $total = array_sum(array_map(fn($risk) => $risk->getRiskScore(), $this->packageRisks));
        return $total / count($this->packageRisks);
    }

    private function getGlobalRecommendations(): array
    {
        $highRiskCount = count($this->getHighRiskPackages());

        if ($highRiskCount > 10) {
            return [
                'Consider comprehensive dependency audit',
                'Implement automated security monitoring',
                'Review package selection policies',
            ];
        } elseif ($highRiskCount > 5) {
            return [
                'Monitor high-risk packages closely',
                'Consider alternatives for highest-risk packages',
            ];
        }

        return ['Continue regular monitoring'];
    }
}

class PackageRisk
{
    private array $data;

    public function __construct(array $data)
    {
        $this->data = $data;
    }

    public function getRiskScore(): float
    {
        return $this->data['risk_score'] ?? 0.0;
    }

    public function toArray(): array
    {
        return $this->data;
    }
}

๐Ÿ“š Best Practices and Resources

Development Team Training

PHP SBOM Workshop Curriculum
# PHP SBOM Workshop: From Basics to Advanced

## Day 1: Foundations
### Morning Session
- Understanding Software Bills of Materials
- PHP ecosystem security challenges
- Composer dependency management review
- Introduction to CycloneDX format

### Afternoon Session
- Hands-on: First SBOM generation
- SBOM validation and quality checking
- Integration with development workflow
- Common issues and troubleshooting

## Day 2: Advanced Implementation
### Morning Session
- Framework-specific implementations (Laravel, Symfony)
- CI/CD pipeline integration
- Security analysis and vulnerability scanning
- License compliance automation

### Afternoon Session
- Enterprise deployment strategies
- Performance optimization for large projects
- Custom tooling and automation
- Future trends and best practices

## Resources Provided
- Sample projects for each major PHP framework
- CI/CD pipeline templates
- Security policy configurations
- Enterprise deployment guides

Community Resources

Essential PHP SBOM Resources

Industry Implementation Examples

Success Stories
  • E-commerce Platform: Reduced security incident response time by 75%
  • Financial Services: Achieved SOX compliance with automated SBOM generation
  • Healthcare Application: Met HIPAA requirements through comprehensive dependency tracking
  • Government Contractor: Satisfied federal SBOM requirements with CycloneDX integration

Conclusion

PHP SBOM generation has evolved from a security best practice to a business necessity. With Composer's rich dependency ecosystem and the powerful CycloneDX PHP tools, PHP developers have world-class capabilities for creating comprehensive, accurate Software Bills of Materials.

Key Success Factors:
  • Start with Standards: Use CycloneDX format for maximum compatibility
  • Integrate Early: Build SBOM generation into development workflows from day one
  • Automate Everything: Use CI/CD pipelines for consistent, reliable SBOM generation
  • Monitor Continuously: Combine SBOM generation with security and license monitoring
  • Scale Strategically: Use parallel processing and memory-efficient techniques for enterprise applications
The PHP Advantage: โœ… Mature Ecosystem - Rich tooling with excellent Composer integration โœ… Framework Support - Native integration with Laravel, Symfony, and modern PHP frameworks โœ… Security Focus - Built-in security audit capabilities and vulnerability scanning โœ… Performance Optimized - Efficient processing of large dependency trees โœ… Enterprise Ready - Scales from individual projects to enterprise monorepos Your PHP SBOM journey starts with a single command. Install the CycloneDX PHP Composer tool today, generate your first SBOM, and begin building the transparency and security that modern PHP applications demand.

The future of PHP development is transparent, secure, and compliantโ€”and it starts with comprehensive Software Bills of Materials that protect your applications and your business. ๐Ÿš€