Skip to content

Commit

Permalink
Merge pull request #57 from jboctor/patch-1
Browse files Browse the repository at this point in the history
Update TypeField to be inheritable and transitive
  • Loading branch information
Crell authored Jun 5, 2024
2 parents 244d4ad + b6ff45f commit a1154b3
Show file tree
Hide file tree
Showing 10 changed files with 227 additions and 3 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ Updates should follow the [Keep a CHANGELOG](http://keepachangelog.com/) princip
This release includes a small *breaking change*. The deformatter methods all now have nullable returns. This is necessary to allow for deserializing values that are legitimately and permissibly null. If you do not have any custom Importers, you should not be impacted. If you do have a custom Importer, you *may* need to adjust your logic to account for the return value from the deformatter being null.

### Added
- Nothing
- TypeField is now Transitive, so you can implement a custom TypeField for a specific object, and it will apply anywhere it is used.
- TypeField is now Inheritable, too.

### Deprecated
- Nothing
Expand Down
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
},
"scripts": {
"test": "XDEBUG_MODE=coverage phpunit",
"phpstan": "php -d memory_limit=140M vendor/bin/phpstan",
"phpstan": "php -d memory_limit=256M vendor/bin/phpstan",
"coverage": "php -dextension=pcov.so -dpcov.enabled=1 -dpcov.directory=src vendor/bin/phpunit --coverage-text",
"all-checks": [
"@test",
Expand Down
5 changes: 4 additions & 1 deletion src/TypeField.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@

namespace Crell\Serde;

interface TypeField
use Crell\AttributeUtils\Inheritable;
use Crell\AttributeUtils\TransitiveProperty;

interface TypeField extends TransitiveProperty, Inheritable
{
/**
* Determines if this field is valid for a given type.
Expand Down
22 changes: 22 additions & 0 deletions tests/Attributes/ReducingTransitiveTypeField.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

declare(strict_types=1);

namespace Crell\Serde\Attributes;

use Attribute;
use Crell\Serde\TypeField;

#[Attribute(Attribute::TARGET_CLASS)]
class ReducingTransitiveTypeField implements TypeField
{
public function acceptsType(string $type): bool
{
return $type === 'object' || class_exists($type) || interface_exists($type);
}

public function validate(mixed $value): bool
{
return true;
}
}
22 changes: 22 additions & 0 deletions tests/Attributes/TransitiveTypeField.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

declare(strict_types=1);

namespace Crell\Serde\Attributes;

use Attribute;
use Crell\Serde\TypeField;

#[Attribute(Attribute::TARGET_CLASS)]
class TransitiveTypeField implements TypeField
{
public function acceptsType(string $type): bool
{
return true;
}

public function validate(mixed $value): bool
{
return true;
}
}
13 changes: 13 additions & 0 deletions tests/Records/ClassWithPropertyWithTransitiveTypeField.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

declare(strict_types=1);

namespace Crell\Serde\Records;

class ClassWithPropertyWithTransitiveTypeField
{
public function __construct(
public readonly TransitiveField $transitive,
) {
}
}
12 changes: 12 additions & 0 deletions tests/Records/ClassWithReducibleProperty.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

declare(strict_types=1);

namespace Crell\Serde\Records;

class ClassWithReducibleProperty
{
public function __construct(
public ReducibleClass $dbReference,
) {}
}
16 changes: 16 additions & 0 deletions tests/Records/ReducibleClass.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

declare(strict_types=1);

namespace Crell\Serde\Records;

use Crell\Serde\Attributes\ReducingTransitiveTypeField;

#[ReducingTransitiveTypeField]
class ReducibleClass
{
public function __construct(
public int $id,
public string $name,
) {}
}
16 changes: 16 additions & 0 deletions tests/Records/TransitiveField.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

declare(strict_types=1);

namespace Crell\Serde\Records;

use Crell\Serde\Attributes\TransitiveTypeField;

#[TransitiveTypeField]
class TransitiveField
{
public function __construct(
public readonly string $name = 'Transitive',
) {
}
}
119 changes: 119 additions & 0 deletions tests/SerdeTestCases.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,22 @@
namespace Crell\Serde;

use Crell\Serde\Attributes\ClassNameTypeMap;
use Crell\Serde\Attributes\Field;
use Crell\Serde\Attributes\ReducingTransitiveTypeField;
use Crell\Serde\Attributes\StaticTypeMap;
use Crell\Serde\Attributes\TransitiveTypeField;
use Crell\Serde\Formatter\SupportsCollecting;
use Crell\Serde\PropertyHandler\Exporter;
use Crell\Serde\PropertyHandler\ObjectExporter;
use Crell\Serde\PropertyHandler\ObjectImporter;
use Crell\Serde\Records\AliasedFields;
use Crell\Serde\Records\AllFieldTypes;
use Crell\Serde\Records\BackedSize;
use Crell\Serde\Records\Callbacks\CallbackHost;
use Crell\Serde\Records\CircularReference;
use Crell\Serde\Records\ClassWithDefaultRenaming;
use Crell\Serde\Records\ClassWithPropertyWithTransitiveTypeField;
use Crell\Serde\Records\ClassWithReducibleProperty;
use Crell\Serde\Records\DateTimeExample;
use Crell\Serde\Records\DictionaryKeyTypes;
use Crell\Serde\Records\Drupal\EmailItem;
Expand Down Expand Up @@ -57,6 +65,7 @@
use Crell\Serde\Records\Pagination\ProductType;
use Crell\Serde\Records\Pagination\Results;
use Crell\Serde\Records\Point;
use Crell\Serde\Records\ReducibleClass;
use Crell\Serde\Records\RequiresFieldValues;
use Crell\Serde\Records\RequiresFieldValuesClass;
use Crell\Serde\Records\RootMap\Type;
Expand All @@ -74,6 +83,7 @@
use Crell\Serde\Records\Tasks\SmallTask;
use Crell\Serde\Records\Tasks\Task;
use Crell\Serde\Records\Tasks\TaskContainer;
use Crell\Serde\Records\TransitiveField;
use Crell\Serde\Records\TraversableInts;
use Crell\Serde\Records\TraversablePoints;
use Crell\Serde\Records\Traversables;
Expand Down Expand Up @@ -1754,4 +1764,113 @@ public function multiple_same_class_value_objects_work_when_nested_and_flattened

}

#[Test]
public function transitive_type_field_is_recognized(): void
{
$exporter = new class () extends ObjectExporter {
public bool $wasCalled = false;

public function exportValue(Serializer $serializer, Field $field, mixed $value, mixed $runningValue): mixed
{
$this->wasCalled = true;
return parent::exportValue($serializer, $field, $value, $runningValue);
}

public function canExport(Field $field, mixed $value, string $format): bool
{
return $field->typeField instanceof TransitiveTypeField;
}
};
$importer = new class() extends ObjectImporter {
public bool $wasCalled = false;

public function importValue(Deserializer $deserializer, Field $field, mixed $source): mixed
{
$this->wasCalled = true;
return parent::importValue($deserializer, $field, $source);
}

public function canImport(Field $field, string $format): bool
{
return $field->typeField instanceof TransitiveTypeField;
}
};

$s = new SerdeCommon(handlers: [$exporter, $importer], formatters: $this->formatters);

$data = new ClassWithPropertyWithTransitiveTypeField(new TransitiveField('Beep'));

$serialized = $s->serialize($data, $this->format);

$this->transitive_type_field_is_recognized_validate($serialized);

$result = $s->deserialize($serialized, from: $this->format, to: $data::class);

self::assertEquals($data, $result);
self::assertTrue($exporter->wasCalled);
self::assertTrue($importer->wasCalled);
}

public function transitive_type_field_is_recognized_validate(mixed $serialized): void
{

}

#[Test]
public function objects_can_be_reduced_to_primitive(): void
{
$exporter = new class () implements Exporter {
public bool $wasCalled = false;

public function exportValue(Serializer $serializer, Field $field, mixed $value, mixed $runningValue): mixed
{
$this->wasCalled = true;
return $serializer->formatter->serializeInt($runningValue, $field, $value->id);
}

public function canExport(Field $field, mixed $value, string $format): bool
{
return $field->typeField instanceof ReducingTransitiveTypeField;
}
};
$importer = new class() extends ObjectImporter {
public bool $wasCalled = false;

public function importValue(Deserializer $deserializer, Field $field, mixed $source): mixed
{
$this->wasCalled = true;
$id = $deserializer->deformatter->deserializeInt($source, $field);

return match ($id) {
1 => new ReducibleClass(1, 'One'),
2 => new ReducibleClass(2, 'Two'),
default => throw new \Exception('Record not found'),
};
}

public function canImport(Field $field, string $format): bool
{
return $field->typeField instanceof ReducingTransitiveTypeField;
}
};

$s = new SerdeCommon(handlers: [$exporter, $importer], formatters: $this->formatters);

$data = new ClassWithReducibleProperty(new ReducibleClass(1, 'One'));

$serialized = $s->serialize($data, $this->format);

$this->objects_can_be_reduced_to_primitive_validate($serialized);

$result = $s->deserialize($serialized, from: $this->format, to: $data::class);

self::assertEquals($data, $result);
self::assertTrue($exporter->wasCalled);
self::assertTrue($importer->wasCalled);
}

public function objects_can_be_reduced_to_primitive_validate(mixed $serialized): void
{

}
}

0 comments on commit a1154b3

Please sign in to comment.