Neue Services und Klassen hinzugefügt: Projekt-Entitäten, Repository und Konfigurationsverwaltung
This commit is contained in:
21
src/Attributes/Mapping/DataFromArray.php
Normal file
21
src/Attributes/Mapping/DataFromArray.php
Normal 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,
|
||||
){}
|
||||
}
|
||||
0
src/Entity/.gitignore
vendored
0
src/Entity/.gitignore
vendored
74
src/Entity/Project.php
Normal file
74
src/Entity/Project.php
Normal 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)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
0
src/Repository/.gitignore
vendored
0
src/Repository/.gitignore
vendored
57
src/Repository/ProjectRepository.php
Normal file
57
src/Repository/ProjectRepository.php
Normal 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);
|
||||
}
|
||||
}
|
||||
125
src/Service/ProjectConfigDirService.php
Normal file
125
src/Service/ProjectConfigDirService.php
Normal 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 [];
|
||||
}
|
||||
}
|
||||
23
src/Traits/LoggerTrait.php
Normal file
23
src/Traits/LoggerTrait.php
Normal 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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user