diff --git a/README.md b/README.md index 0c75d95..06d1f9f 100644 --- a/README.md +++ b/README.md @@ -228,8 +228,8 @@ src/ | Metric | Value | |---|---| -| PHP source files | 19 | -| Source lines | ~720 | +| PHP source files | 20 | +| Source lines | ~810 | | Test files | 13 | | Test lines | ~700 | | External runtime dependencies | 1 (kariricode/property-inspector) | diff --git a/src/Attribute/Serialize.php b/src/Attribute/Serialize.php index dc12ce6..a311dc8 100644 --- a/src/Attribute/Serialize.php +++ b/src/Attribute/Serialize.php @@ -7,7 +7,12 @@ /** * Marks a property for serialization control. * - * Parameters: name (wire-name override), groups (string[]), format-specific options. + * Parameters: name (wire-name override), groups (string[]), ignore (bool). + * Properties without this attribute are included automatically using their property name. + * + * @package KaririCode\Serializer\Attribute + * @author Walmir Silva + * @since 3.1.0 ARFA 1.3 */ #[\Attribute(\Attribute::TARGET_PROPERTY)] final readonly class Serialize diff --git a/src/Configuration/SerializerConfiguration.php b/src/Configuration/SerializerConfiguration.php index 3e00c6a..7ece789 100644 --- a/src/Configuration/SerializerConfiguration.php +++ b/src/Configuration/SerializerConfiguration.php @@ -4,6 +4,13 @@ namespace KaririCode\Serializer\Configuration; +/** + * Immutable configuration value object for the serializer engine. + * + * @package KaririCode\Serializer\Configuration + * @author Walmir Silva + * @since 3.1.0 ARFA 1.3 + */ final readonly class SerializerConfiguration { public function __construct( diff --git a/src/Contract/EncoderRegistry.php b/src/Contract/EncoderRegistry.php index aa9bf49..8dafa79 100644 --- a/src/Contract/EncoderRegistry.php +++ b/src/Contract/EncoderRegistry.php @@ -4,6 +4,13 @@ namespace KaririCode\Serializer\Contract; +/** + * Contract for format-encoder registries. + * + * @package KaririCode\Serializer\Contract + * @author Walmir Silva + * @since 3.1.0 ARFA 1.3 + */ interface EncoderRegistry { public function register(Encoder $encoder): void; diff --git a/src/Core/InMemoryEncoderRegistry.php b/src/Core/InMemoryEncoderRegistry.php index d424168..f30a0ee 100644 --- a/src/Core/InMemoryEncoderRegistry.php +++ b/src/Core/InMemoryEncoderRegistry.php @@ -8,6 +8,15 @@ use KaririCode\Serializer\Contract\EncoderRegistry; use KaririCode\Serializer\Exception\SerializationException; +/** + * In-memory encoder registry backed by a plain PHP array. + * + * Throws SerializationException on duplicate registration or unknown format lookup. + * + * @package KaririCode\Serializer\Core + * @author Walmir Silva + * @since 3.1.0 ARFA 1.3 + */ final class InMemoryEncoderRegistry implements EncoderRegistry { /** @var array */ diff --git a/src/Core/SerializationContextImpl.php b/src/Core/SerializationContextImpl.php index 080a70a..c0d65a6 100644 --- a/src/Core/SerializationContextImpl.php +++ b/src/Core/SerializationContextImpl.php @@ -6,6 +6,16 @@ use KaririCode\Serializer\Contract\SerializationContext; +/** + * Immutable serialization context carrying the active format and parameters. + * + * Uses the named-constructor pattern: instantiate via `SerializationContextImpl::create()`. + * `withFormat()` and `withParameters()` return new instances (full immutability). + * + * @package KaririCode\Serializer\Core + * @author Walmir Silva + * @since 3.1.0 ARFA 1.3 + */ final readonly class SerializationContextImpl implements SerializationContext { /** diff --git a/src/Core/SerializerEngine.php b/src/Core/SerializerEngine.php index a93b5a5..4bac6e8 100644 --- a/src/Core/SerializerEngine.php +++ b/src/Core/SerializerEngine.php @@ -12,6 +12,8 @@ * Central serialization orchestrator. * * Delegates to format-specific encoders via the registry. + * Resolves the active format from the request or falls back to the + * configured default, then delegates encode/decode to the matching encoder. * * @package KaririCode\Serializer\Core * @author Walmir Silva @@ -34,20 +36,20 @@ public function __construct( public function serialize(array $data, ?string $format = null, array $parameters = []): SerializationResult { $config = $this->configuration ?? new SerializerConfiguration(); - $fmt = $format ?? $config->defaultFormat; - $encoder = $this->registry->resolve($fmt); + $resolvedFormat = $format ?? $config->defaultFormat; + $encoder = $this->registry->resolve($resolvedFormat); - $ctx = SerializationContextImpl::create($fmt); + $serializationContext = SerializationContextImpl::create($resolvedFormat); if ($config->prettyPrint) { - $ctx = $ctx->withParameters(['pretty' => true]); + $serializationContext = $serializationContext->withParameters(['pretty' => true]); } if ($parameters !== []) { - $ctx = $ctx->withParameters($parameters); + $serializationContext = $serializationContext->withParameters($parameters); } - $payload = $encoder->encode($data, $ctx); + $payload = $encoder->encode($data, $serializationContext); - return new SerializationResult($data, $payload, $fmt); + return new SerializationResult($data, $payload, $resolvedFormat); } /** @@ -59,14 +61,14 @@ public function serialize(array $data, ?string $format = null, array $parameters public function deserialize(string $payload, ?string $format = null, array $parameters = []): array { $config = $this->configuration ?? new SerializerConfiguration(); - $fmt = $format ?? $config->defaultFormat; - $encoder = $this->registry->resolve($fmt); + $resolvedFormat = $format ?? $config->defaultFormat; + $encoder = $this->registry->resolve($resolvedFormat); - $ctx = SerializationContextImpl::create($fmt); + $serializationContext = SerializationContextImpl::create($resolvedFormat); if ($parameters !== []) { - $ctx = $ctx->withParameters($parameters); + $serializationContext = $serializationContext->withParameters($parameters); } - return $encoder->decode($payload, $ctx); + return $encoder->decode($payload, $serializationContext); } } diff --git a/src/Encoder/CsvEncoder.php b/src/Encoder/CsvEncoder.php index a33ac14..8921b6c 100644 --- a/src/Encoder/CsvEncoder.php +++ b/src/Encoder/CsvEncoder.php @@ -9,9 +9,13 @@ use KaririCode\Serializer\Exception\SerializationException; /** - * Encodes/decodes flat arrays-of-arrays as CSV. + * Encodes/decodes flat arrays-of-arrays as RFC 4180-compliant CSV via php://temp. * * Parameters: separator (string, ','), enclosure (string, '"'), header (bool, true). + * + * @package KaririCode\Serializer\Encoder + * @author Walmir Silva + * @since 3.1.0 ARFA 1.3 */ final readonly class CsvEncoder implements Encoder { @@ -38,13 +42,13 @@ public function encode(array $data, SerializationContext $context): string if ($hasHeader && \is_array($firstRow)) { /** @var array $keys */ $keys = array_keys($firstRow); - fputcsv($stream, $keys, $separator, $enclosure); + fputcsv($stream, $keys, $separator, $enclosure, escape: '\\'); } foreach ($data as $row) { if (\is_array($row)) { /** @var array $row */ - fputcsv($stream, $row, $separator, $enclosure); + fputcsv($stream, $row, $separator, $enclosure, escape: '\\'); } } @@ -75,7 +79,7 @@ public function decode(string $payload, SerializationContext $context): array } $rows = array_values( - array_map(static fn (string $line) => str_getcsv($line, $separator, $enclosure), $lines), + array_map(static fn (string $line) => str_getcsv($line, $separator, $enclosure, escape: '\\'), $lines), ); if ($hasHeader && \count($rows) > 1) { diff --git a/src/Encoder/JsonEncoder.php b/src/Encoder/JsonEncoder.php index 619de84..350ef65 100644 --- a/src/Encoder/JsonEncoder.php +++ b/src/Encoder/JsonEncoder.php @@ -8,6 +8,16 @@ use KaririCode\Serializer\Contract\SerializationContext; use KaririCode\Serializer\Exception\SerializationException; +/** + * Encodes/decodes data as RFC 8259-compliant JSON. + * + * Uses JSON_THROW_ON_ERROR for deterministic error handling. + * Supports `pretty` context parameter for human-readable output. + * + * @package KaririCode\Serializer\Encoder + * @author Walmir Silva + * @since 3.1.0 ARFA 1.3 + */ final readonly class JsonEncoder implements Encoder { /** @param array $data */ diff --git a/src/Encoder/QueryStringEncoder.php b/src/Encoder/QueryStringEncoder.php index e23c69e..a13f69c 100644 --- a/src/Encoder/QueryStringEncoder.php +++ b/src/Encoder/QueryStringEncoder.php @@ -7,7 +7,13 @@ use KaririCode\Serializer\Contract\Encoder; use KaririCode\Serializer\Contract\SerializationContext; -/** Encodes/decodes URL query strings (application/x-www-form-urlencoded). */ +/** + * Encodes/decodes data as RFC 3986-compliant URL query strings (application/x-www-form-urlencoded). + * + * @package KaririCode\Serializer\Encoder + * @author Walmir Silva + * @since 3.1.0 ARFA 1.3 + */ final readonly class QueryStringEncoder implements Encoder { /** @param array $data */ diff --git a/src/Encoder/XmlEncoder.php b/src/Encoder/XmlEncoder.php index 8b2d90c..947b148 100644 --- a/src/Encoder/XmlEncoder.php +++ b/src/Encoder/XmlEncoder.php @@ -8,6 +8,16 @@ use KaririCode\Serializer\Contract\SerializationContext; use KaririCode\Serializer\Exception\SerializationException; +/** + * Encodes/decodes arrays as application/xml via SimpleXMLElement + DOMDocument. + * + * Uses `libxml_use_internal_errors` to suppress PHP notices on malformed XML decode. + * Supports `root` and `pretty` context parameters. + * + * @package KaririCode\Serializer\Encoder + * @author Walmir Silva + * @since 3.1.0 ARFA 1.3 + */ final readonly class XmlEncoder implements Encoder { /** @param array $data */ diff --git a/src/Event/SerializationCompletedEvent.php b/src/Event/SerializationCompletedEvent.php index 83bf620..e8b1e7a 100644 --- a/src/Event/SerializationCompletedEvent.php +++ b/src/Event/SerializationCompletedEvent.php @@ -6,6 +6,13 @@ use KaririCode\Serializer\Result\SerializationResult; +/** + * Event emitted when a serialization operation completes successfully. + * + * @package KaririCode\Serializer\Event + * @author Walmir Silva + * @since 3.1.0 ARFA 1.3 + */ final readonly class SerializationCompletedEvent { public function __construct(public SerializationResult $result, public float $durationMs, public float $timestamp = 0) diff --git a/src/Event/SerializationStartedEvent.php b/src/Event/SerializationStartedEvent.php index 6b18915..2217e89 100644 --- a/src/Event/SerializationStartedEvent.php +++ b/src/Event/SerializationStartedEvent.php @@ -4,6 +4,13 @@ namespace KaririCode\Serializer\Event; +/** + * Event emitted when a serialization operation begins. + * + * @package KaririCode\Serializer\Event + * @author Walmir Silva + * @since 3.1.0 ARFA 1.3 + */ final readonly class SerializationStartedEvent { public function __construct(public string $format, public float $timestamp = 0) diff --git a/src/Exception/SerializationException.php b/src/Exception/SerializationException.php index ca0198c..ac094a0 100644 --- a/src/Exception/SerializationException.php +++ b/src/Exception/SerializationException.php @@ -4,6 +4,15 @@ namespace KaririCode\Serializer\Exception; +/** + * Domain exception for all serialization and deserialization failures. + * + * Uses static factory methods for type-safe construction. + * + * @package KaririCode\Serializer\Exception + * @author Walmir Silva + * @since 3.1.0 ARFA 1.3 + */ final class SerializationException extends \RuntimeException { public static function encodingFailed(string $format, string $reason): self diff --git a/src/Integration/ProcessorBridge.php b/src/Integration/ProcessorBridge.php index 226c31d..9d2b847 100644 --- a/src/Integration/ProcessorBridge.php +++ b/src/Integration/ProcessorBridge.php @@ -7,6 +7,15 @@ use KaririCode\Serializer\Core\SerializerEngine; use KaririCode\Serializer\Result\SerializationResult; +/** + * Bridges the processor-pipeline to the serializer engine. + * + * Adapts the generic Pipeline interface to format-specific serialization output. + * + * @package KaririCode\Serializer\Integration + * @author Walmir Silva + * @since 3.1.0 ARFA 1.3 + */ final readonly class ProcessorBridge { public function __construct( diff --git a/src/Result/SerializationResult.php b/src/Result/SerializationResult.php index 0181be7..2ba7abc 100644 --- a/src/Result/SerializationResult.php +++ b/src/Result/SerializationResult.php @@ -7,6 +7,7 @@ /** * Immutable result of a serialization pass. * + * @package KaririCode\Serializer\Result * @author Walmir Silva * @since 3.1.0 ARFA 1.3 */ diff --git a/tests/Conformance/ImmutableStateTest.php b/tests/Conformance/ImmutableStateTest.php index d0c11e0..14e8074 100644 --- a/tests/Conformance/ImmutableStateTest.php +++ b/tests/Conformance/ImmutableStateTest.php @@ -5,10 +5,14 @@ namespace KaririCode\Serializer\Tests\Conformance; use KaririCode\Serializer\Core\SerializationContextImpl; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Test; use PHPUnit\Framework\TestCase; +#[CoversClass(SerializationContextImpl::class)] final class ImmutableStateTest extends TestCase { + #[Test] public function testContextImmutability(): void { $ctx = SerializationContextImpl::create('json'); @@ -18,6 +22,7 @@ public function testContextImmutability(): void $this->assertSame('xml', $ctx2->getFormat()); } + #[Test] public function testParametersImmutability(): void { $ctx = SerializationContextImpl::create();