Skip to content

Commit 46cb91a

Browse files
committed
Preliminary work on an analyzer implementation that works by deserializing YAML from disk.
1 parent a8a305a commit 46cb91a

File tree

5 files changed

+179
-0
lines changed

5 files changed

+179
-0
lines changed

src/Analyzer/YamlFileAnalyzer.php

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Crell\Serde\Analyzer;
6+
7+
use Crell\AttributeUtils\Analyzer;
8+
use Crell\AttributeUtils\ClassAnalyzer;
9+
use Crell\Serde\Serde;
10+
use Crell\Serde\SerdeCommon;
11+
use Symfony\Component\Yaml\Yaml;
12+
13+
/**
14+
* @todo Instead of a cache-style directory, let callers specify the exact file to read.
15+
* The intent isn't to use as a cache, but to let people hand-write YAML files instead of
16+
* using attributes.
17+
*/
18+
class YamlFileAnalyzer implements ClassAnalyzer
19+
{
20+
private readonly string $directory;
21+
22+
public function __construct(
23+
string $directory,
24+
private readonly Serde $serde = new SerdeCommon(new Analyzer()),
25+
) {
26+
$this->directory = rtrim($directory, '/\\');
27+
}
28+
29+
public function save(object $data, string $class, string $attribute, array $scopes = []): void
30+
{
31+
$yaml = $this->serde->serialize($data, format: 'yaml');
32+
33+
$filename = $this->getFileName($class, $attribute, $scopes);
34+
35+
$this->ensureDirectory($filename);
36+
37+
file_put_contents($filename, $yaml);
38+
}
39+
40+
private function ensureDirectory(string $filename): void
41+
{
42+
$dir = dirname($filename);
43+
if (!is_dir($dir)) {
44+
if (!mkdir($dir, 0777, true) && !is_dir($dir)) {
45+
throw new \RuntimeException(sprintf('Directory "%s" was not created', $dir));
46+
}
47+
}
48+
}
49+
50+
private function getFileName(string $class, string $attribute, array $scopes): string
51+
{
52+
return $this->directory
53+
. DIRECTORY_SEPARATOR
54+
. str_replace('\\', '_', $attribute)
55+
. DIRECTORY_SEPARATOR
56+
. str_replace('\\', '_', $class)
57+
. DIRECTORY_SEPARATOR
58+
. (implode('_', $scopes) ?: 'all_scopes')
59+
. '.yaml';
60+
}
61+
62+
public function analyze(object|string $class, string $attribute, array $scopes = []): object
63+
{
64+
// Everything is easier if we normalize to a class first.
65+
// Because anon classes have generated internal class names, they work, too.
66+
$class = is_string($class) ? $class : $class::class;
67+
68+
$classFile = $this->getFileName($class, $attribute, $scopes);
69+
70+
$yaml = Yaml::parseFile($classFile);
71+
72+
$result = $this->serde->deserialize($yaml, from: 'array', to: $attribute);
73+
74+
return $result;
75+
}
76+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Crell\Serde\Analyzer\Attributes;
6+
7+
use Crell\AttributeUtils\Attributes\Reflect\CollectProperties;
8+
use Crell\AttributeUtils\Attributes\Reflect\ReflectProperty;
9+
use Crell\AttributeUtils\ParseProperties;
10+
use Crell\Serde\Attributes\DictionaryField;
11+
use Crell\Serde\Attributes\SequenceField;
12+
13+
#[\Attribute]
14+
class Stuff implements ParseProperties
15+
{
16+
/** @var Stuff[] */
17+
#[DictionaryField(arrayType: Thing::class)]
18+
public readonly array $properties;
19+
20+
public function setProperties(array $properties): void
21+
{
22+
$this->properties = $properties;
23+
}
24+
25+
public function includePropertiesByDefault(): bool
26+
{
27+
return true;
28+
}
29+
30+
public function __construct(
31+
public readonly string $a,
32+
public readonly string $b = '',
33+
) {}
34+
35+
public function propertyAttribute(): string
36+
{
37+
return Thing::class;
38+
}
39+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Crell\Serde\Analyzer\Attributes;
6+
7+
#[\Attribute]
8+
class Thing
9+
{
10+
public function __construct(
11+
public readonly int $a = 0,
12+
public readonly int $b = 0,
13+
) {}
14+
}

tests/Analyzer/Records/Dummy.php

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Crell\Serde\Analyzer\Records;
6+
7+
use Crell\Serde\Analyzer\Attributes\Stuff;
8+
use Crell\Serde\Analyzer\Attributes\Thing;
9+
10+
#[Stuff(a: 'hello')]
11+
class Dummy
12+
{
13+
public function __construct(
14+
#[Thing(5)]
15+
public readonly string $a = 'a',
16+
public readonly string $b = 'b',
17+
) {}
18+
}

tests/SerializedAnalyzerTest.php

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Crell\Serde;
6+
7+
use Crell\AttributeUtils\Analyzer;
8+
use Crell\Serde\Analyzer\Attributes\Stuff;
9+
use Crell\Serde\Analyzer\Records\Dummy;
10+
use Crell\Serde\Analyzer\YamlFileAnalyzer;
11+
use PHPUnit\Framework\TestCase;
12+
13+
class SerializedAnalyzerTest extends TestCase
14+
{
15+
16+
/**
17+
* @test
18+
*/
19+
public function stuff(): void
20+
{
21+
$analyzer = new YamlFileAnalyzer('/tmp/yamlanalyzer');
22+
23+
$attributeAnalyzer = new Analyzer();
24+
$classSettings = $attributeAnalyzer->analyze(Dummy::class, Stuff::class);
25+
26+
$analyzer->save($classSettings, Dummy::class, Stuff::class);
27+
28+
$result = $analyzer->analyze(Dummy::class, Stuff::class);
29+
30+
self::assertEquals($classSettings, $result);
31+
}
32+
}

0 commit comments

Comments
 (0)