FileSystemComposerClient.php•3.84 kB
<?php
declare(strict_types=1);
namespace Butschster\ContextGenerator\Lib\ComposerClient;
use Butschster\ContextGenerator\Application\Logger\LoggerPrefix;
use Butschster\ContextGenerator\Source\Composer\Exception\ComposerNotFoundException;
use Psr\Log\LoggerInterface;
use Spiral\Files\Exception\FilesException;
use Spiral\Files\FilesInterface;
/**
 * Client that interacts with Composer package data through the filesystem
 */
final readonly class FileSystemComposerClient implements ComposerClientInterface
{
    public function __construct(
        private FilesInterface $files,
        #[LoggerPrefix(prefix: 'composer-client')]
        private ?LoggerInterface $logger = null,
    ) {}
    public function loadComposerData(string $path): array
    {
        // If path is a directory, append composer.json
        if (\is_dir($path)) {
            $path = \rtrim($path, '/') . '/composer.json';
        }
        // Check if composer.json exists
        if (!$this->files->exists($path)) {
            $this->logger?->error('composer.json not found', ['path' => $path]);
            throw ComposerNotFoundException::fileNotFound($path);
        }
        // Read composer.json
        $composerJson = $this->files->read($path);
        // Parse composer.json
        $composerData = \json_decode($composerJson, true);
        if (!\is_array($composerData) || \json_last_error() !== JSON_ERROR_NONE) {
            $this->logger?->error('Failed to parse composer.json', [
                'path' => $path,
                'error' => \json_last_error_msg(),
            ]);
            throw ComposerNotFoundException::cannotParse($path, \json_last_error_msg());
        }
        return $composerData;
    }
    public function tryLoadComposerLock(string $path): ?array
    {
        $lockPath = \rtrim($path, '/') . '/composer.lock';
        if (!$this->files->exists($lockPath)) {
            $this->logger?->info('composer.lock not found', ['path' => $lockPath]);
            return null;
        }
        // Read composer.lock
        try {
            $lockJson = $this->files->read($lockPath);
        } catch (FilesException $e) {
            $this->logger?->error('Failed to read composer.lock', [
                'path' => $lockPath,
                'error' => $e->getMessage(),
            ]);
            return null;
        }
        // Parse composer.lock
        $lockData = \json_decode($lockJson, true);
        if (!\is_array($lockData) || \json_last_error() !== JSON_ERROR_NONE) {
            $this->logger?->warning('Failed to parse composer.lock', [
                'path' => $lockPath,
                'error' => \json_last_error_msg(),
            ]);
            return null;
        }
        return $lockData;
    }
    public function getVendorDir(array $composerData, string $basePath): string
    {
        // Check if composer.json has a custom vendor-dir configuration
        if (isset($composerData['config']['vendor-dir']) && \is_string($composerData['config']['vendor-dir'])) {
            return $composerData['config']['vendor-dir'];
        }
        // Check if vendor directory exists in the base path
        $defaultVendorDir = 'vendor';
        $vendorPath = $basePath . '/' . $defaultVendorDir;
        if (\is_dir($vendorPath)) {
            return $defaultVendorDir;
        }
        // If vendor directory doesn't exist, try to find it
        $possibleVendorDirs = ['vendor', 'vendors', 'lib', 'libs', 'packages', 'deps'];
        foreach ($possibleVendorDirs as $dir) {
            if (\is_dir($basePath . '/' . $dir)) {
                $this->logger?->info('Found alternative vendor directory', ['directory' => $dir]);
                return $dir;
            }
        }
        // Default to 'vendor' if nothing else is found
        return $defaultVendorDir;
    }
}