Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 76 additions & 0 deletions src/Analyzer/YamlFileAnalyzer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
<?php

declare(strict_types=1);

namespace Crell\Serde\Analyzer;

use Crell\AttributeUtils\Analyzer;
use Crell\AttributeUtils\ClassAnalyzer;
use Crell\Serde\Serde;
use Crell\Serde\SerdeCommon;
use Symfony\Component\Yaml\Yaml;

/**
* @todo Instead of a cache-style directory, let callers specify the exact file to read.
* The intent isn't to use as a cache, but to let people hand-write YAML files instead of
* using attributes.
*/
class YamlFileAnalyzer implements ClassAnalyzer
{
private readonly string $directory;

public function __construct(
string $directory,
private readonly Serde $serde = new SerdeCommon(new Analyzer()),
) {
$this->directory = rtrim($directory, '/\\');
}

public function save(object $data, string $class, string $attribute, array $scopes = []): void
{
$yaml = $this->serde->serialize($data, format: 'yaml');

$filename = $this->getFileName($class, $attribute, $scopes);

$this->ensureDirectory($filename);

file_put_contents($filename, $yaml);
}

private function ensureDirectory(string $filename): void
{
$dir = dirname($filename);
if (!is_dir($dir)) {
if (!mkdir($dir, 0777, true) && !is_dir($dir)) {
throw new \RuntimeException(sprintf('Directory "%s" was not created', $dir));
}
}
}

private function getFileName(string $class, string $attribute, array $scopes): string
{
return $this->directory
. DIRECTORY_SEPARATOR
. str_replace('\\', '_', $attribute)
. DIRECTORY_SEPARATOR
. str_replace('\\', '_', $class)
. DIRECTORY_SEPARATOR
. (implode('_', $scopes) ?: 'all_scopes')
. '.yaml';
}

public function analyze(object|string $class, string $attribute, array $scopes = []): object
{
// Everything is easier if we normalize to a class first.
// Because anon classes have generated internal class names, they work, too.
$class = is_string($class) ? $class : $class::class;

$classFile = $this->getFileName($class, $attribute, $scopes);

$yaml = Yaml::parseFile($classFile);

$result = $this->serde->deserialize($yaml, from: 'array', to: $attribute);

return $result;
}
}
39 changes: 39 additions & 0 deletions tests/Analyzer/Attributes/Stuff.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?php

declare(strict_types=1);

namespace Crell\Serde\Analyzer\Attributes;

use Crell\AttributeUtils\Attributes\Reflect\CollectProperties;
use Crell\AttributeUtils\Attributes\Reflect\ReflectProperty;
use Crell\AttributeUtils\ParseProperties;
use Crell\Serde\Attributes\DictionaryField;
use Crell\Serde\Attributes\SequenceField;

#[\Attribute]
class Stuff implements ParseProperties
{
/** @var Stuff[] */
#[DictionaryField(arrayType: Thing::class)]
public readonly array $properties;

public function setProperties(array $properties): void
{
$this->properties = $properties;
}

public function includePropertiesByDefault(): bool
{
return true;
}

public function __construct(
public readonly string $a,
public readonly string $b = '',
) {}

public function propertyAttribute(): string
{
return Thing::class;
}
}
14 changes: 14 additions & 0 deletions tests/Analyzer/Attributes/Thing.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php

declare(strict_types=1);

namespace Crell\Serde\Analyzer\Attributes;

#[\Attribute]
class Thing
{
public function __construct(
public readonly int $a = 0,
public readonly int $b = 0,
) {}
}
18 changes: 18 additions & 0 deletions tests/Analyzer/Records/Dummy.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

declare(strict_types=1);

namespace Crell\Serde\Analyzer\Records;

use Crell\Serde\Analyzer\Attributes\Stuff;
use Crell\Serde\Analyzer\Attributes\Thing;

#[Stuff(a: 'hello')]
class Dummy
{
public function __construct(
#[Thing(5)]
public readonly string $a = 'a',
public readonly string $b = 'b',
) {}
}
32 changes: 32 additions & 0 deletions tests/SerializedAnalyzerTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

declare(strict_types=1);

namespace Crell\Serde;

use Crell\AttributeUtils\Analyzer;
use Crell\Serde\Analyzer\Attributes\Stuff;
use Crell\Serde\Analyzer\Records\Dummy;
use Crell\Serde\Analyzer\YamlFileAnalyzer;
use PHPUnit\Framework\TestCase;

class SerializedAnalyzerTest extends TestCase
{

/**
* @test
*/
public function stuff(): void
{
$analyzer = new YamlFileAnalyzer('/tmp/yamlanalyzer');

$attributeAnalyzer = new Analyzer();
$classSettings = $attributeAnalyzer->analyze(Dummy::class, Stuff::class);

$analyzer->save($classSettings, Dummy::class, Stuff::class);

$result = $analyzer->analyze(Dummy::class, Stuff::class);

self::assertEquals($classSettings, $result);
}
}