Skip to content

Commit

Permalink
Preliminary work on an analyzer implementation that works by deserial…
Browse files Browse the repository at this point in the history
…izing YAML from disk.
  • Loading branch information
Crell committed Mar 2, 2023
1 parent a8a305a commit 46cb91a
Show file tree
Hide file tree
Showing 5 changed files with 179 additions and 0 deletions.
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);
}
}

0 comments on commit 46cb91a

Please sign in to comment.