diff --git a/CHANGELOG.md b/CHANGELOG.md index fe2e6ad..6434384 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ ## Unreleased +### Added +- Introduced `JsonMapper::createWithDefaults()` to bootstrap the mapper with Symfony reflection, PhpDoc extractors, and a default property accessor. + ### Changed - Marked `MagicSunday\\JsonMapper\\JsonMapper` as `final` and promoted constructor dependencies to `readonly` properties for consistent visibility. - Declared `MagicSunday\\JsonMapper\\Converter\\CamelCasePropertyNameConverter` as `final` and immutable. diff --git a/README.md b/README.md index d25ab76..aaaf95a 100644 --- a/README.md +++ b/README.md @@ -65,23 +65,13 @@ require __DIR__ . '/vendor/autoload.php'; use App\Dto\Article; use App\Dto\ArticleCollection; use MagicSunday\JsonMapper; -use Symfony\Component\PropertyAccess\PropertyAccess; -use Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor; -use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor; -use Symfony\Component\PropertyInfo\PropertyInfoExtractor; // Decode a single article and a list of articles, raising on malformed JSON. $single = json_decode('{"title":"Hello world","comments":[{"message":"First!"}]}', associative: false, flags: JSON_THROW_ON_ERROR); $list = json_decode('[{"title":"Hello world","comments":[{"message":"First!"}]},{"title":"Second","comments":[]}]', associative: false, flags: JSON_THROW_ON_ERROR); -// Configure JsonMapper with reflection and PhpDoc support. -$propertyInfo = new PropertyInfoExtractor( - listExtractors: [new ReflectionExtractor()], - typeExtractors: [new PhpDocExtractor()], -); -$propertyAccessor = PropertyAccess::createPropertyAccessor(); - -$mapper = new JsonMapper($propertyInfo, $propertyAccessor); +// Bootstrap JsonMapper with reflection and PhpDoc extractors. +$mapper = JsonMapper::createWithDefaults(); // Map a single DTO and an entire collection in one go. $article = $mapper->map($single, Article::class); @@ -93,6 +83,8 @@ var_dump($article, $articles); The first call produces an `Article` instance with a populated `CommentCollection`; the second call returns an `ArticleCollection` containing `Article` objects. +`JsonMapper::createWithDefaults()` wires the default Symfony `PropertyInfoExtractor` (reflection + PhpDoc) and a `PropertyAccessor`. When you need custom extractors, caching, or a specialised accessor you can still instantiate `JsonMapper` manually with your preferred services. + Test coverage: `tests/JsonMapper/DocsQuickStartTest.php`. ### PHP classes diff --git a/docs/API.md b/docs/API.md index 6fd906f..6b8d678 100644 --- a/docs/API.md +++ b/docs/API.md @@ -5,6 +5,18 @@ This document summarises the public surface of the JsonMapper package. All class ## JsonMapper (final) The `JsonMapper` class is the main entry point for mapping arbitrary JSON structures to PHP objects. The class is `final`; prefer composition over inheritance. +### Factory helper +```php +valueConverter->addStrategy(new PassthroughValueConversionStrategy()); } + /** + * Creates a mapper with sensible default Symfony services. + * + * @param PropertyNameConverterInterface|null $nameConverter Optional converter to normalise incoming property names. + * @param array $classMap Optional class map forwarded to the mapper constructor. + * @param CacheItemPoolInterface|null $typeCache Optional cache for resolved type information. + * @param JsonMapperConfiguration|null $config Default mapper configuration cloned for new mapping contexts. + */ + public static function createWithDefaults( + ?PropertyNameConverterInterface $nameConverter = null, + array $classMap = [], + ?CacheItemPoolInterface $typeCache = null, + ?JsonMapperConfiguration $config = null, + ): self { + $extractor = new PropertyInfoExtractor( + [new ReflectionExtractor()], + [new PhpDocExtractor()], + ); + + return new self( + $extractor, + PropertyAccess::createPropertyAccessor(), + $nameConverter, + $classMap, + $typeCache, + $config ?? new JsonMapperConfiguration(), + ); + } + /** * Registers a custom type handler. * diff --git a/tests/Classes/CamelCasePerson.php b/tests/Classes/CamelCasePerson.php new file mode 100644 index 0000000..096de94 --- /dev/null +++ b/tests/Classes/CamelCasePerson.php @@ -0,0 +1,20 @@ + 42, + 'name' => 'Example', + ]; + + $result = $mapper->map($payload, Simple::class); + + self::assertInstanceOf(Simple::class, $result); + self::assertSame(42, $result->id); + self::assertSame('Example', $result->name); + } + + public function testCreateWithDefaultsUsesProvidedNameConverter(): void + { + $mapper = JsonMapper::createWithDefaults(new CamelCasePropertyNameConverter()); + + $payload = (object) [ + 'first_name' => 'Ada', + ]; + + $result = $mapper->map($payload, CamelCasePerson::class); + + self::assertInstanceOf(CamelCasePerson::class, $result); + self::assertSame('Ada', $result->firstName); + } +}