CodeDateiScanner.php
Pfad: src/Infrastructure/Code/CodeDateiScanner.php
Ext: php
Größe: 4536 Bytes
Geändert: 2026-07-16 10:05:26+02
<?php
declare(strict_types=1);
namespace Demo\Infrastructure\Code;
use Demo\Domain\Code\CodeDatei;
use RecursiveDirectoryIterator;
use RecursiveIteratorIterator;
use SplFileInfo;
/**
* Scannt das Projektverzeichnis nach Code-Dateien fuer den Mirror.
*/
final class CodeDateiScanner
{
/** @var string Root des Projekts. */
private string $projektRoot;
/** @var list<string> Dateiendungen, die als Code betrachtet werden. */
private array $erlaubteErweiterungen;
/** @var list<string> Auszuschliessende Ordnernamen (Komponenten). */
private array $ignorierteOrdner;
/**
* @param string $projektRoot Projekt-Root.
* @param list<string> $erlaubteErweiterungen Endungen als Code.
* @param list<string> $ignorierteOrdner Ignorierte Ordner.
*/
public function __construct(
string $projektRoot,
array $erlaubteErweiterungen = ['php', 'js', 'css', 'sql', 'sh', 'yml', 'yaml', 'json'],
array $ignorierteOrdner = ['.git', 'credentials', 'node_modules', 'vendor', 'tmp']
) {
$this->projektRoot = rtrim($projektRoot, '/');
$this->erlaubteErweiterungen = $erlaubteErweiterungen;
$this->ignorierteOrdner = $ignorierteOrdner;
}
/**
* Scannt alle erlaubten Dateien.
*
* @return list<CodeDatei> Dateien mit Inhalt, sortiert nach Pfad.
*/
public function scan(): array
{
$dateien = [];
$iterator = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($this->projektRoot, RecursiveDirectoryIterator::SKIP_DOTS),
RecursiveIteratorIterator::SELF_FIRST
);
foreach ($iterator as $eintrag) {
if (!$eintrag->isFile()) {
continue;
}
$pfad = $eintrag->getPathname();
if (!$this->istCodeDatei($eintrag)) {
continue;
}
if ($this->istIgnoriert($pfad)) {
continue;
}
$inhalt = file_get_contents($pfad);
if ($inhalt === false) {
continue;
}
$dateien[] = $this->baueCodeDatei($eintrag, (string) $inhalt);
}
usort($dateien, static fn (CodeDatei $a, CodeDatei $b): int => strcmp($a->relativerPfad, $b->relativerPfad));
return $dateien;
}
/**
* Erzeugt eine CodeDatei aus einem Dateieintrag.
*
* @param SplFileInfo $datei Metadaten aus dem FS.
* @param string $code Dateicontent.
* @return CodeDatei Datensatz fuer den Spiegel.
*/
private function baueCodeDatei(SplFileInfo $datei, string $code): CodeDatei
{
$relativerPfad = $this->relativePfad($datei->getPathname());
$groesse = (int) $datei->getSize();
return new CodeDatei(
0,
$relativerPfad,
$datei->getFilename(),
strtolower($datei->getExtension()),
$code,
hash('sha256', $code),
$groesse,
gmdate('c', $datei->getMTime()),
gmdate('c', $datei->getCTime())
);
}
/**
* Prüft Extension + Dateigroesse + Lesbarkeit.
*
* @param SplFileInfo $datei Eintrag.
* @return bool true bei beruecksichtigungsfaehig.
*/
private function istCodeDatei(SplFileInfo $datei): bool
{
$groesse = (int) $datei->getSize();
if ($groesse > 2_000_000) {
return false;
}
$ext = strtolower($datei->getExtension());
return in_array($ext, $this->erlaubteErweiterungen, true);
}
/**
* Prueft, ob der absolute Pfad einen ausgeschlossenen Ordner enthaelt.
*
* @param string $pfad Absoluter Dateipfad.
* @return bool true wenn ignoriert.
*/
private function istIgnoriert(string $pfad): bool
{
$norm = str_replace('\\', '/', $pfad);
$teile = explode('/', str_replace($this->projektRoot . '/', '', $norm));
foreach ($teile as $teil) {
if (in_array($teil, $this->ignorierteOrdner, true)) {
return true;
}
}
return false;
}
/**
* Liefert den relativen Pfad zum Projekt-Root.
*
* @param string $absoluterPfad Voller Dateipfad.
* @return string Relativer Pfad.
*/
private function relativePfad(string $absoluterPfad): string
{
return ltrim(str_replace($this->projektRoot . '/', '', str_replace('\\', '/', $absoluterPfad)), '/');
}
}