Neue Services und Klassen hinzugefügt: Projekt-Entitäten, Repository und Konfigurationsverwaltung

This commit is contained in:
2026-03-27 14:23:37 +01:00
parent 6e40a0e85e
commit 790963976a
7 changed files with 300 additions and 0 deletions

View File

@@ -0,0 +1,21 @@
<?php
/*
* DataFromArray.php 2026-03-16 thomas
*
* Copyright (c) 2026 Thomas Schneider <thomas@inter-mundos.de>
* Alle Rechte vorbehalten.
*/
declare(strict_types=1);
namespace App\Attributes\Mapping;
use Attribute;
#[Attribute(Attribute::TARGET_PROPERTY)]
final readonly class DataFromArray
{
public function __construct(
public string|null $key,
){}
}

View File

74
src/Entity/Project.php Normal file
View File

@@ -0,0 +1,74 @@
<?php
/*
* Project.php 2026-03-20 thomas
*
* Copyright (c) 2026 Thomas Schneider <thomas@inter-mundos.de>
* Alle Rechte vorbehalten.
*/
namespace App\Entity;
use App\Attributes\Mapping\DataFromArray;
use App\Service\Deployment\DeploymentInterface;
use ReflectionClass;
use ReflectionException;
class Project
{
#[DataFromArray(key: 'name')]
public readonly string $name;
#[DataFromArray(key: 'deployment')]
public readonly array $deployment;
protected DeploymentInterface $deploymentService;
public function __construct(
array $data,
public string $projectDir,
public string $deployedVersion,
\Closure $deploymentServiceResolver,
){
$this->autoMapData($data);
$this->deploymentService = ($deploymentServiceResolver)()->init($this);
}
public function deploy(string $version = '', string $step = ''): void
{
$this->deploymentService->handle($this, $version, $step);
}
/**
* Automatically maps data from the provided array to object properties
* based on attributes defined in the class.
*
* @param array $data The input data array containing key-value pairs to map.
*/
protected function autoMapData(array $data): void
{
try
{
$class = new ReflectionClass($this);
foreach($class->getProperties() as $property)
{
foreach($property->getAttributes() as $attribute)
{
if($attribute->getName() === DataFromArray::class)
{
$attributeArguments = $attribute->getArguments();
$this->{$property->getName()} = $data[$attributeArguments['key']];
}
}
}
}
catch(ReflectionException $e)
{
}
}
}

View File

View File

@@ -0,0 +1,57 @@
<?php
/*
* ProjectRepository.php 2026-03-15 thomas
*
* Copyright (c) 2026 Thomas Schneider <thomas@inter-mundos.de>
* Alle Rechte vorbehalten.
*/
namespace App\Repository;
use App\Entity\Project;
use App\Service\Deployment\DeploymentServiceResolver;
use App\Service\ProjectConfigDirService;
use Generator;
use Symfony\Component\DependencyInjection\Attribute\AutowireServiceClosure;
use Symfony\Component\Yaml\Yaml;
class ProjectRepository
{
/** @var Project[] */
private array $projects;
public function __construct(
protected ProjectConfigDirService $configDirService,
#[AutowireServiceClosure(DeploymentServiceResolver::class)] \Closure $deploymentServiceResolver,
){
foreach($this->configDirService->getProjectDirContents() as $projectFile)
{
$deployedVersion = $this->configDirService->getDeployedVersion($projectFile->getPath());
$this->projects[$projectFile->getRelativePath()] = new Project(
data: Yaml::parseFile($projectFile->getRealPath()),
projectDir: $projectFile->getPath(),
deployedVersion: $deployedVersion,
deploymentServiceResolver: $deploymentServiceResolver,
);
}
}
public function getAll(): Generator
{
yield from $this->projects;
}
public function findById($id): Project|null
{
return $this->projects[$id] ?? null;
}
public function exists(string $id): bool
{
return array_any($this->projects, fn($project) => $project['id'] === $id);
}
}

View File

@@ -0,0 +1,125 @@
<?php
/*
* ProjectConfigDirService.php 2026-03-15 thomas
*
* Copyright (c) 2026 Thomas Schneider <thomas@inter-mundos.de>
* Alle Rechte vorbehalten.
*/
namespace App\Service;
use App\Traits\LoggerTrait;
use Symfony\Component\Filesystem\Exception\IOExceptionInterface;
use Symfony\Component\Filesystem\Filesystem;
use Symfony\Component\Filesystem\Path;
use Symfony\Component\Finder\Exception\DirectoryNotFoundException;
use Symfony\Component\Finder\Finder;
use Symfony\Component\Finder\SplFileInfo;
class ProjectConfigDirService
{
use LoggerTrait;
const string FILE_NAME_DEPLOYED_VERSION = '.deployed-version';
const string FILE_NAME_DEPLOYMENT_STEP = '.deployment-step';
public function __construct(
protected string $dataDir,
){}
/**
* @return SplFileInfo[]
*/
public function getProjectDirContents(): array
{
return $this->loadProjectDirContents();
}
public function getDeployedVersion(string $projectDir): string
{
$filesystem = new Filesystem();
if(!$filesystem->exists($projectDir . DIRECTORY_SEPARATOR . self::FILE_NAME_DEPLOYED_VERSION))
{
return '';
}
$version = $filesystem->readFile($projectDir . DIRECTORY_SEPARATOR . self::FILE_NAME_DEPLOYED_VERSION);
return trim($version);
}
public function setDeployedVersion(string $projectDir, string $version): void
{
$filesystem = new Filesystem();
$filesystem->dumpFile($projectDir . DIRECTORY_SEPARATOR . self::FILE_NAME_DEPLOYED_VERSION, $version);
}
public function getDeploymentStep(string $projectDir): string
{
$filesystem = new Filesystem();
if(!$filesystem->exists($projectDir . DIRECTORY_SEPARATOR . self::FILE_NAME_DEPLOYMENT_STEP))
{
return 'not-started';
}
$stage = $filesystem->readFile($projectDir . DIRECTORY_SEPARATOR . self::FILE_NAME_DEPLOYMENT_STEP);
return trim($stage);
}
public function setDeploymentStep(string $projectDir, string $step): void
{
$filesystem = new Filesystem();
$filesystem->dumpFile($projectDir . DIRECTORY_SEPARATOR . self::FILE_NAME_DEPLOYMENT_STEP, $step);
}
/**
* @return SplFileInfo[]
*/
protected function loadProjectDirContents(): array
{
$finder = new Finder();
$content = [];
// find all files in the current directory
try
{
$finder->files()->in($this->dataDir)->depth(1)->name('project.yaml');
foreach($finder as $file)
{
$content[] = $file;
}
return $content;
}
catch(DirectoryNotFoundException $e)
{
$this->logger->error('Project directory not found: ' . $this->dataDir . '. creating ...');
$filesystem = new Filesystem();
try
{
$filesystem->mkdir(Path::normalize($this->dataDir));
$this->loadProjectDirContents();
}
catch(IOExceptionInterface $exception)
{
echo "An error occurred while creating your directory at ".$exception->getPath();
}
}
catch(\Exception $e)
{
throw new \RuntimeException('Failed to load project directory contents', 500, $e);
}
return [];
}
}

View File

@@ -0,0 +1,23 @@
<?php
/*
* LoggerTrait.php 2026-03-12 thomas
*
* Copyright (c) 2026 Thomas Schneider <thomas@inter-mundos.de>
* Alle Rechte vorbehalten.
*/
namespace App\Traits;
use Psr\Log\LoggerInterface;
use Symfony\Contracts\Service\Attribute\Required;
trait LoggerTrait
{
protected LoggerInterface $logger;
#[Required]
public function setLogger(LoggerInterface $logger): void
{
$this->logger = $logger;
}
}