{"id":19477015,"url":"https://github.com/opportus/object-mapper","last_synced_at":"2025-04-25T14:32:30.842Z","repository":{"id":45557541,"uuid":"139037124","full_name":"opportus/object-mapper","owner":"opportus","description":"Maps generically data from source to target object via extensible strategies and controls","archived":false,"fork":false,"pushed_at":"2022-05-25T09:59:47.000Z","size":679,"stargazers_count":18,"open_issues_count":0,"forks_count":1,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-04-03T23:04:41.286Z","etag":null,"topics":["composer-package","data-transformation","data-transformer","dto","dto-generator","mapper","mapping","object-mapper","object-mapping","php","transformer"],"latest_commit_sha":null,"homepage":"","language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/opportus.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":".github/CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":".github/CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2018-06-28T15:30:34.000Z","updated_at":"2024-08-15T08:07:41.000Z","dependencies_parsed_at":"2022-08-23T20:50:12.831Z","dependency_job_id":null,"html_url":"https://github.com/opportus/object-mapper","commit_stats":null,"previous_names":[],"tags_count":26,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/opportus%2Fobject-mapper","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/opportus%2Fobject-mapper/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/opportus%2Fobject-mapper/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/opportus%2Fobject-mapper/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/opportus","download_url":"https://codeload.github.com/opportus/object-mapper/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":250834206,"owners_count":21494929,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["composer-package","data-transformation","data-transformer","dto","dto-generator","mapper","mapping","object-mapper","object-mapping","php","transformer"],"created_at":"2024-11-10T19:43:21.476Z","updated_at":"2025-04-25T14:32:30.176Z","avatar_url":"https://github.com/opportus.png","language":"PHP","readme":"# Object Mapper\n\n[![License](https://poser.pugx.org/opportus/object-mapper/license)](https://packagist.org/packages/opportus/object-mapper)\n[![Latest Stable Version](https://poser.pugx.org/opportus/object-mapper/v/stable)](https://packagist.org/packages/opportus/object-mapper)\n[![Latest Unstable Version](https://poser.pugx.org/opportus/object-mapper/v/unstable)](https://packagist.org/packages/opportus/object-mapper)\n[![Build](https://github.com/opportus/object-mapper/workflows/Build/badge.svg)](https://github.com/opportus/object-mapper/actions?query=workflow%3ABuild)\n[![Codacy Badge](https://app.codacy.com/project/badge/Coverage/d3f5178323844f59a6ef5647cb11d9d7)](https://www.codacy.com/manual/opportus/object-mapper/dashboard?utm_source=github.com\u0026utm_medium=referral\u0026utm_content=opportus/object-mapper\u0026utm_campaign=Badge_Coverage)\n[![Codacy Badge](https://api.codacy.com/project/badge/Grade/d3f5178323844f59a6ef5647cb11d9d7)](https://www.codacy.com/manual/opportus/object-mapper?utm_source=github.com\u0026amp;utm_medium=referral\u0026amp;utm_content=opportus/object-mapper\u0026amp;utm_campaign=Badge_Grade)\n\n**Index**\n\n- [Meta](#meta)\n- [Introduction](#introduction)\n- [Roadmap](#roadmap)\n  - [v1.1.0](#v110)\n- [Integrations](#integrations)\n- [Setup](#setup)\n  - [Step 1 - Installation](#step-1---installation)\n  - [Step 2 - Initialization](#step-2---initialization)\n- [Mapping](#mapping)\n  - [Overview](#overview)\n  - [Automatic Mapping](#automatic-mapping)\n    - [Static Path Finder](#static-path-finder)\n    - [Static Source To Dynamic Target Path Finder](#static-source-to-dynamic-target-path-finder)\n    - [Dynamic Source To Static Target Path Finder](#dynamic-source-to-static-target-path-finder)\n    - [Custom Path Finder](#custom-path-finder)\n  - [Manual Mapping](#manual-mapping)\n    - [Via Map Builder API](#via-map-builder-api)\n    - [Via Map Definition Preloading](#via-map-definition-preloading)\n  - [Check Point](#check-point)\n  - [Recursion](#recursion)\n\n## Meta\n\nThis document is a guide mainly walking you through the setup, concepts, and use\ncases of this solution.\n\nAPI documentation is bound to code and complies to PHPDoc standards...\n\nSecondary sections are collapsed in order to not overflow the first reading.\n\n## Introduction\n\nUse this solution for mapping generically data from source to target object via\nextensible strategies and controls.\n\nDelegate responsibility of all of your data mapping to a generic - extensible -\noptimized - tested mapping system.\n\nLeverage that system to:\n \n-   Decouple your codebase from data mapping logic\n-   Dynamically define control flow over data being transferred from source to\n    target\n-   Dynamically define target model based on source model and vice-versa\n-   Easily genericize - centralize - optimize - test - execute data mapping\n-   Design efficiently and simplify your system\n\nThis project aims to provide a standard core system to higher level systems\nsuch as:\n\n-   ORM\n-   Form handler\n-   Serializer\n-   Data import\n-   Layers data representation mapper\n-   ...\n\nIf you need to map from/to `array` data structure, simply cast it to/from\n`object` `stdClass`.\n\n## Roadmap\n\nTo develop this solution faster, [contributions](https://github.com/opportus/object-mapper/blob/master/.github/CONTRIBUTING.md) are welcome...\n\n### v1.1.0\n\n-   Implement recursion path finder feature\n-   Implement callable check point feature\n-   Implement seizing check point feature\n-   Improve doc\n\n## Integrations\n\n-   Symfony 4 application =\u003e [opportus/object-mapper-bundle](https://github.com/opportus/ObjectMapperBundle)\n-   {{ reference_here_your_own_integration }}\n\n## Setup\n\n### Step 1 - Installation\n\nOpen a command console, enter your project directory and execute:\n\n```console\n$ composer require opportus/object-mapper\n```\n\n### Step 2 - Initialization\n\nThis library contains 4 services. 3 of them require a single dependency which is\nanother lower level service among those 4:\n\n```php\nuse Opportus\\ObjectMapper\\Point\\PointFactory;\nuse Opportus\\ObjectMapper\\Route\\RouteBuilder;\nuse Opportus\\ObjectMapper\\Map\\MapBuilder;\nuse Opportus\\ObjectMapper\\ObjectMapper;\n\n$pointFactory = new PointFactory();\n$routeBuilder = new RouteBuilder($pointFactory);\n$mapBuilder   = new MapBuilder($routeBuilder);\n$objectMapper = new ObjectMapper($mapBuilder);\n```\n\nIn order for the *object mapper* to get properly initialized, each of its\nservices must be instantiated such as above.\n\nBy design, this solution does not provide \"helpers\" for the instantiation of\nits own services which is much better handled the way you're already\ninstantiating your own services, with a DIC system or whatever.\n\n## Mapping\n\n### Overview\n\nIn order to transfer data from a *source* object to a *target* object, the\n[`ObjectMapper`](https://github.com/opportus/object-mapper/blob/master/src/ObjectMapper.php)\niterates through each\n[`Route`](https://github.com/opportus/object-mapper/blob/master/src/Route/Route.php)\nthat it gets from a\n[`Map`](https://github.com/opportus/object-mapper/blob/master/src/Map/Map.php),\nassigning the value of the current *route*'s *source point* to this *route*'s\n*target point*.\n\nOptionally, on the *route*, *check points* can be defined in\norder to control the value from the *source point* before it reaches the\n*target point*.\n\nA *route* is defined by and composed of its *source point*, its *target point*,\nand its *check points*.\n\nA *source point* can be either:\n\n-   A property\n-   A method\n-   Any extended type of *source point*\n\nA *target point* can be either:\n\n-   A property\n-   A method parameter\n-   Any extended type of *target point*\n\nA *check point* can be any implementation of\n[`CheckPointInterface`](https://github.com/opportus/object-mapper/blob/master/src/Point/CheckPointInterface.php).\n\nThese *routes* can be defined [automatically](#automatic-mapping) via a *map*'s\n[`PathFinderInterface`](https://github.com/opportus/object-mapper/blob/master/src/PathFinder/PathFinderInterface.php)\nstrategy implementation and/or [manually](#manual-mapping) via:\n\n-   [Map builder API](#via-map-builder-api)\n-   [Map definition preloading](#via-map-definition-preloading)\n\n### Automatic Mapping\n\nRemember that `PathFinderInterface` implementations such as those covered next\nin this section can get combined.\n\n#### Static Path Finder\n\nA basic example of how to automatically map `User`'s data to `UserDto` and\nvice-versa:\n\n```php\nclass User\n{\n    private $username;\n\n    public function __construct(string $username)\n    {\n        $this-\u003eusername = $username;\n    }\n\n    public function getUsername(): string\n    {\n        return $this-\u003eusername;\n    }\n}\n\nclass UserDto\n{\n    public $username;\n}\n\n$user    = new User('Toto');\n$userDto = new UserDto();\n\n// Map the data of the User instance to the UserDto instance\n$objectMapper-\u003emap($user, $userDto);\n\necho $userDto-\u003eusername; // Toto\n\n// Map the data of the UserDto instance to a new User instance\n$user = $objectMapper-\u003emap($userDto, User::class);\n\necho $user-\u003egetUsername(); // Toto\n```\n\nCalling the `ObjectMapper::map()` method passing no `$map` argument makes\nthe method build then use a `Map` composed of the default `StaticPathFinder`\nstrategy.\n\nThe default `StaticPathFinder` strategy determines the appropriate point of the\n*source* class to connect to each point of the *target* class. Doing so, it\ndefines a *route* to follow by the *object mapper*.\n\nFor the default [`StaticPathFinder`](https://github.com/opportus/object-mapper/blob/master/src/PathFinder/StaticPathFinder.php),\na reference *target point* can be:\n\n-   A public property ([`PropertyStaticTargetPoint`](https://github.com/opportus/object-mapper/blob/master/src/Point/PropertyStaticTargetPoint.php))\n-   A parameter of a public setter or constructor ([`MethodParameterStaticTargetPoint`](https://github.com/opportus/object-mapper/blob/master/src/Point/MethodParameterStaticTargetPoint.php))\n\nThe corresponding *source point* can be:\n\n-   A public property having for name the same as the *target point* ([`PropertyStaticSourcePoint`](https://github.com/opportus/object-mapper/blob/master/src/Point/PropertyStaticSourcePoint.php))\n-   A public getter having for name `'get'.ucfirst($targetPointName)` and\n    requiring no argument ([`MethodStaticSourcePoint`](https://github.com/opportus/object-mapper/blob/master/src/Point/MethodStaticSourcePoint.php))\n\n#### Static Source To Dynamic Target Path Finder\n\n\u003cdetails\u003e\n\n\u003csummary\u003eClick for details\u003c/summary\u003e\n\nA basic example of how to automatically map `User`'s data to `DynamicUserDto`:\n\n```php\nclass DynamicUserDto {}\n\n$user    = new User('Toto');\n$userDto = new DynamicUserDto();\n\n// Build the map\n$map = $mapBuilder\n    -\u003eaddStaticSourceToDynamicTargetPathFinder()\n    -\u003egetMap();\n\n// Map the data of the User instance to the DynamicUserDto instance\n$objectMapper-\u003emap($user, $userDto, $map);\n\necho $userDto-\u003eusername; // Toto\n```\n\nThe default `StaticSourceToDynamicTargetPathFinder` strategy determines the\nappropriate point of the *target* **object** (*dynamic point*) to connect to\neach point of the *source* **class** (*static point*).\n\nFor the default [`StaticSourceToDynamicTargetPathFinder`](https://github.com/opportus/object-mapper/blob/master/src/PathFinder/StaticSourceToDynamicTargetPathFinder.php),\na reference *source point* can be:\n\n-   A public property ([`PropertyStaticSourcePoint`](https://github.com/opportus/object-mapper/blob/master/src/Point/PropertyStaticSourcePoint.php))\n-   A public getter requiring no argument ([`MethodStaticSourcePoint`](https://github.com/opportus/object-mapper/blob/master/src/Point/MethodStaticSourcePoint.php))\n\nThe corresponding *target point* can be:\n\n-   A statically undefined (not existing in class) property having for name the\n    same as the property *source point* or\n    `lcfirst(substr($getterSourcePoint, 3))`\n    ([`PropertyDynamicTargetPoint`](https://github.com/opportus/object-mapper/blob/master/src/Point/PropertyDynamicTargetPoint.php))\n\n\u003c/details\u003e\n\n#### Dynamic Source To Static Target Path Finder\n\n\u003cdetails\u003e\n\n\u003csummary\u003eClick for details\u003c/summary\u003e\n\nA basic example of how to automatically map `DynamicUserDto`'s data to `User`:\n\n```php\nclass DynamicUserDto {}\n\n$userDto = new DynamicUserDto();\n$userDto-\u003eusername = 'Toto';\n\n// Build the map\n$map = $mapBuilder\n    -\u003eaddDynamicSourceToStaticTargetPathFinder()\n    -\u003egetMap();\n\n// Map the data of the DynamicUserDto instance to a new User instance\n$user = $objectMapper-\u003emap($userDto, User::class, $map);\n\necho $user-\u003egetUsername(); // Toto\n```\n\nThe default `DynamicSourceToStaticTargetPathFinder` strategy determines the\nappropriate point of the *source* **object** (*dynamic point*) to connect to\neach point of the *target* **class** (*static point*). \n\nFor the default [`StaticSourceToDynamicTargetPathFinder`](https://github.com/opportus/object-mapper/blob/master/src/PathFinder/DynamicSourceToStaticTargetPathFinder.php),\na reference *target point* can be:\n\n-   A public property ([`PropertyStaticTargetPoint`](https://github.com/opportus/object-mapper/blob/master/src/Point/PropertyStaticTargetPoint.php))\n-   A parameter of a public setter or constructor ([`MethodParameterStaticTargetPoint`](https://github.com/opportus/object-mapper/blob/master/src/Point/MethodParameterStaticTargetPoint.php))\n\nThe corresponding *source point* can be:\n\n-   A statically undefined (not existing in class) but dynamically defined\n    (existing in object) property having for name the same as the *target point*\n    ([`PropertyDynamicSourcePoint`](https://github.com/opportus/object-mapper/blob/master/src/Point/PropertyDynamicSourcePoint.php))\n\n\u003c/details\u003e\n\n#### Custom Path Finder\n\nThe default *path finders* presented above implement each a specific mapping\nlogic. In order for those to generically map differently typed objects, they\nhave to follow a certain convention de facto established by these\n*path finders*. You can map generically differently typed objects only\naccordingly to the *path finders* the *map* is composed of.\n\nIf the default *path finders* do not suit your needs, you still can genericize\nand encapsulate your domain's mapping logic as subtype(s) of\n`PathFinderInterface`. Doing so effectively, you leverage *Object Mapper* to\ndecouple these objects from your mapping logic... Indeed, when the mapped\nobjects change, the mapping doesn't.\n\nFor best example of how to implement [`PathFinderInterface`](https://github.com/opportus/object-mapper/blob/master/src/PathFinder/PathFinderInterface.php), refer to the default [`StaticPathFinder`](https://github.com/opportus/object-mapper/blob/master/src/PathFinder/StaticPathFinder.php), [`StaticSourceToDynamicTargetPathFinder`](https://github.com/opportus/object-mapper/blob/master/src/PathFinder/StaticSourceToDynamicTargetPathFinder.php), and\n[`DynamicSourceToStaticTargetPathFinder`](https://github.com/opportus/object-mapper/blob/master/src/PathFinder/DynamicSourceToStaticTargetPathFinder.php)\nimplementations.\n\n**Example**\n\n```php\nclass MyPathFinder implements PathFinderInterface\n{\n    private $routeBuilder;\n\n    // ...\n\n    public function getRoutes(SourceInterface $source, TargetInterface $target): RouteCollection\n    {\n        $source-\u003egetClassReflection();\n        $target-\u003egetClassReflection();\n        \n        $routes = [];\n\n        /**\n         * Custom mapping algorithm based on source/target relection and\n         * possibly their data...\n         * \n         * Use route builder to build routes...\n         */\n\n        return new RouteCollection($routes);\n    }\n}\n\n// Pass to the map builder pathfinders you want it to compose the map of\n$map = $mapBuilder\n    -\u003eaddStaticPathFinder()\n    -\u003eaddPathFinder(new MyPathFinder($routeBuilder))\n    -\u003egetMap();\n\n// Use the map\n$user = $objectMapper-\u003emap($userDto, User::class, $map);\n```\n\n### Manual Mapping\n\nIf in your context, such as walked through in the previous\n\"[automatic mapping](#automatic-mapping)\" section, a mapping strategy is\nimpossible, you can manually map the *source* to the *target*.\n\nThere are multiple ways to define manually the mapping such as introduced in the\n2 next sub-sections:\n\n-   [Via map builder API](#via-map-builder-api)\n-   [Via map definition preloading](#via-map-definition-preloading)\n\n#### Via Map Builder API\n\n\u003cdetails\u003e\n\n\u003csummary\u003eClick for details\u003c/summary\u003e\n\nThe [`MapBuilder`](https://github.com/opportus/object-mapper/blob/master/src/Map/MapBuilder.php)\nis an immutable service which implement a fluent interface.\n\nA basic example of how to manually map `User`'s data to `ContributorDto` and\nvice-versa with the `MapBuilder`:\n\n```php\nclass User\n{\n    private $username;\n\n    public function __construct(string $username)\n    {\n        $this-\u003eusername = $username;\n    }\n\n    public function getUsername(): string\n    {\n        return $this-\u003eusername;\n    }\n}\n\nclass ContributorDto\n{\n    public $name;\n}\n\n$user = new User('Toto');\n$contributorDto = new ContributorDto();\n\n// Define the route manually\n$map = $mapBuilder\n    -\u003egetRouteBuilder()\n        -\u003esetStaticSourcePoint('User::getUsername()')\n        -\u003esetStaticTargetPoint('ContributorDto::$name')\n        -\u003eaddRouteToMapBuilder()\n        -\u003egetMapBuilder()\n    -\u003egetMap();\n\n// Map the data of the User instance to the ContributorDto instance\n$objectMapper-\u003emap($user, $contributorDto, $map);\n\necho $contributorDto-\u003ename; // Toto\n\n// Define the route manually\n$map = $mapBuilder\n    -\u003egetRouteBuilder()\n        -\u003esetStaticSourcePoint('ContributorDto::$name')\n        -\u003esetStaticTargetPoint('User::__construct()::$username')\n        -\u003eaddRouteToMapBuilder()\n        -\u003egetMapBuilder()\n    -\u003egetMap();\n\n// Map the data of the ContributorDto instance to a new User instance\n$user = $objectMapper-\u003emap($contributorDto, User::class, $map);\n\necho $user-\u003egetUsername(); // 'Toto'\n```\n\n\u003c/details\u003e\n\n#### Via Map Definition Preloading\n\n\u003cdetails\u003e\n\n\u003csummary\u003eClick for details\u003c/summary\u003e\n\n[Via the map builder API](#via-map-builder-api) presented above, we define the\n*map* (adding to it *routes*) *on the go*. There is another way to define the\n*map*, *preloading* its definition.\n\nWhile this library is designed with *map definition preloading* in mind, it\ndoes not provide a way to effectively *preload a map definition* which could be:\n\n-   Any type of file, commonly used for configuration (XML, YAML, JSON, etc...),\n    defining statically a *map* to build at runtime\n-   Any type of annotation in *source* and *target* classes, defining statically\n    a *map* to build at runtime\n-   Any type of PHP routine, defining dynamically a *map* to build at runtime\n-   ...\n\nA *map* being not much more than a collection of *routes*, you can statically\ndefine it for example by defining its *routes* FQN this way:\n\n```yaml\nmap:\n  - source1::$property=\u003etarget1::$property\n  - source1::$property=\u003etarget2::$property\n  - source2::$property=\u003etarget2::$property\n```\n\nThen at runtime, in order to create *routes* to compose a *map* of, you can:\n\n-   Parse your map configuration files, extract from them *route* definitions\n-   Parse your *source* and *target* annotations, extract from them *route*\n    definitions\n-   Implement any sort of map generator logic outputing *route* definitions\n\nThen, based on their definitions, build these *routes* with the initial instance\nof the [`MapBuilder`](https://github.com/opportus/object-mapper/blob/master/src/Map/MapBuilderInterface.php)\nwhich will keep and inject them into its built *maps* which in turn might return\nthese *routes* to the *object mapper* depending on the source and target being\nmapped.\n\nBecause an *object mapper* has a wide range of different use case contexts, this\nsolution is designed as a minimalist, flexible, and extensible core in order to\nget integrated, adapted, and extended seamlessly into any of these contexts.\nTherefore, this solution delegates *map definition preloading* to the\nintegrating higher level system which can make use contextually of its own DIC,\nconfiguration, and cache systems required for achieving\n*map definition preloading*.\n\n[opportus/object-mapper-bundle](https://github.com/opportus/ObjectMapperBundle)\nis one system integrating this library (into Symfony 4 application context).\nYou can refer to it for concrete examples of how to implement\n*map definition preloading*.\n\n\u003c/details\u003e\n\n### Check Point\n\nA *check point*, added to a *route*, allows you to control/transform the value\nfrom the *source point* before it reaches the *target point*.\n\nYou can add multiple *check points* to a *route*. In this case, these\n*check points* form a chain. The first *check point* controls the original value\nfrom the *source point* and returns the value (transformed or not) to the\n*object mapper*. Then, the *object mapper* passes the value to the next\n*check point* and so on... Until the last *check point* returns the final value\nto be assigned to the *target point* by the *object mapper*.\n\nSo it is important to keep in mind that each *check point* has a unique position\n(priority) on a *route*. The routed value goes through each of the\n*check points* from the lowest to the highest positioned ones such as\nrepresented below:\n\n```txt\nSourcePoint --\u003e $value' --\u003e CheckPoint1 --\u003e $value'' --\u003e CheckPoint2 --\u003e $value''' --\u003e TargetPoint\n```\n\nA simple example implementing [`CheckPointInterface`](https://github.com/opportus/object-mapper/blob/master/src/Point/CheckPointInterface.php), and `PathFinderInterface`, to form what we could call a presentation layer:\n\n```php\nclass Contributor\n{\n    private $bio;\n\n    public function __construct(string $bio)\n    {\n        $this-\u003ebio = $bio;\n    }\n\n    public function getBio(): string\n    {\n        return $this-\u003ebio;\n    }\n}\n\nclass ContributorView\n{\n    public $bio;\n}\n\nclass GenericViewHtmlTagStripper implements CheckPointInterface\n{\n    public function control($value, RouteInterface $route, MapInterface $map, SourceInterface $source, TargetInterface $target)\n    {\n        return \\strip_tags($value);\n    }\n}\n\nclass GenericViewMarkdownTransformer implements CheckPointInterface\n{\n    // ...\n    public function control($value, RouteInterface $route, MapInterface $map, SourceInterface $source, TargetInterface $target)\n    {\n        return $this-\u003emarkdownParser-\u003etransform($value);\n    }\n}\n\nclass GenericPresentation extends StaticPathFinder\n{\n    // ...\n    public function getRoutes(Source $source, Target $target): RouteCollection\n    {\n        $routes = parent::getRoutes($source, $target);\n\n        $controlledRoutes = [];\n\n        foreach ($routes as $route) {\n            $controlledRoutes[] = $this-\u003erouteBuilder\n                -\u003esetSourcePoint($route-\u003egetSourcePoint()-\u003egetFqn())\n                -\u003esetTargetPoint($route-\u003egetTargetPoint()-\u003egetFqn())\n                -\u003eaddCheckPoint(new GenericViewHtmlTagStripper(), 10)\n                -\u003eaddCheckPoint(new GenericViewMarkdownTransformer($this-\u003emarkdownParser), 20)\n                -\u003egetRoute();\n        }\n\n        return new RouteCollection($controlledRoutes);\n    }\n}\n\n$contributor = new Contributor('\u003cscript\u003e**Hello World!**\u003c/script\u003e');\n\n$map = $mapBuilder\n    -\u003eaddPathFinder(new GenericPresentation($markdownTransformer))\n    -\u003egetMap();\n\n$contributorView = $objectMapper-\u003emap($contributor, ContributorView::class, $map);\n\necho $contributorView-\u003ebio; // \u003cb\u003eHello World!\u003c/b\u003e\n```\n\nIn this example, based on the *Object Mapper*'s abilities, we code a whole\napplication generic layer with no effort...\n\nBut what is a layer? Accordingly to\n[Wikipedia](https://en.wikipedia.org/wiki/Abstraction_layer):\n\n\u003e An abstraction layer is a way of hiding the working details of a subsystem, allowing the separation of concerns to facilitate interoperability and platform independence.\n\nThe more the *root* system (say an application) has independent layers, the more\nit has\n[data representations](https://guides.library.ucla.edu/c.php?g=180580\u0026p=1191498),\nthe more it has to map data from one representation to another.\n\nThink for example of the Clean Architecture:\n\n- Controller maps its (POST) request representation to its corresponding\n  interactor/usecase request representation\n- Interactor maps its usecase request representation to its corresponding\n  domain entity representation\n- Entity gateway maps its domain entity representation to its\n  corresponding persistence representation, and vice-versa\n- Presenter maps its domain entity representation to its corresponding\n  view representation\n\nEach of these layers' essence is to map data based on the logic they are\ncomposed of. This logic is what we can calle the *flow of control* over data.\n\nReferring to our example... This flow of control is defined by the\n*path finder*. These controls are our *check points*. The `ObjectMapper`\nservice is nothing but that concrete layered system. Such layered OOP system is\nan *object mapper*.\n\n### Recursion\n\nA *recursion* implements [`CheckPointInterface`](https://github.com/opportus/object-mapper/blob/master/src/Point/CheckPointInterface.php).\nIt is used to recursively map a *source point* to a *target point*.\n\nThis means:\n\n-   During mapping an instance of `A` (that *has* `C`) to `B` (that *has* `D`),\n    mapping in same time `C` to `D`, AKA *simple recursion*.\n-   During mapping an instance of `A` (that *has many* `C`) to `B`\n    (that *has many* `D`), mapping in same time many `C` to many `D`, AKA\n    *in-width recursion* or *iterable recursion*.\n-   During mapping an instance of `A` (that *has* `C` which *has* `E`) to `B`\n    (that *has* `D` which *has* `F`), mapping in same time `C` and `E` to `D`\n    and `F`, AKA *in-depth recursion*.\n\nAn example of how to manually map a `Post` and its composite objects to its\n`PostDto` and its composite DTO objects:\n\n```php\nclass Post\n{\n    public Author $author;\n    public Comment[] $comments;\n}\n\nclass Author\n{\n    public string $name;\n}\n\nclass Comment\n{\n    public Author $author;\n}\n\nclass PostDto {}\nclass AuthorDto {}\nclass CommentDto {}\n\n$comment1 = new Comment();\n$comment1-\u003eauthor = new Author();\n$comment1-\u003eauthor-\u003ename = 'clem';\n\n$comment2 = new Comment();\n$comment2-\u003eauthor = new Author();\n$comment2-\u003eauthor-\u003ename = 'bob';\n\n$post = new Post();\n$post-\u003eauthor = new Author();\n$post-\u003eauthor-\u003ename = 'Martin Fowler';\n$post-\u003ecomments = [$comment1, $comment2];\n\n// Let's map the Post instance above and its composites to a new PostDto instance and DTO composites...\n$mapBuilder\n    -\u003egetRouteBuilder\n        -\u003esetStaticSourcePoint('Post::$author')\n        -\u003esetDynamicTargetPoint('PostDto::$author')\n        -\u003eaddRecursionCheckPoint('Author', 'AuthorDto', 'PostDto::$author') // Mapping also Post's Author to PostDto's AuthorDto\n        -\u003eaddRouteToMapBuilder()\n\n        -\u003esetStaticSourcePoint('Comment::$author')\n        -\u003esetDynamicTargetPoint('CommentDto::$author')\n        -\u003eaddRecursionCheckPoint('Author', 'AuthorDto', 'CommentDto::$author') // Mapping also Comment's Author to CommentDto's AuthorDto\n        -\u003eaddRouteToMapBuilder()\n\n        -\u003esetStaticSourcePoint('Post::$comments')\n        -\u003esetDynamicTargetPoint('PostDto::$comments')\n        -\u003eaddIterableRecursionCheckPoint('Comment', 'CommentDto', 'PostDto::$comments') // Mapping also Post's Comment's to PostDto's CommentDto's\n        -\u003eaddRouteToMapBuilder()\n    -\u003egetMapBuilder()\n    -\u003eaddStaticSourceToDynamicTargetPathFinder()\n    -\u003egetMap();\n\n$postDto = $objectMapper-\u003e($post, PostDto::class, $map)\n\nget_class($postDto); // PostDto\n\nget_class($postDto-\u003eauthor); // AuthorDto\necho $postDto-\u003eauthor-\u003ename; // Matin Fowler\n\nget_class($postDto-\u003ecomments[0]); // CommentDto\nget_class($postDto-\u003ecomments[0]-\u003eauthor); // AuthorDto\necho $postDto-\u003ecomments[0]-\u003eauthor-\u003ename; // clem\n\nget_class($postDto-\u003ecomments[1]); // CommentDto\nget_class($postDto-\u003ecomments[1]-\u003eauthor); // AuthorDto\necho $postDto-\u003ecomments[1]-\u003eauthor-\u003ename; // bob\n```\n\nNaturally, all that can get simplified with a higher level `PathFinderInterface`\nimplementation defining these recursions automatically based on source and\ntarget point types. These types being hinted in source and target classes\neither with PHP or PHPDoc.\n\nThis library may feature such `PathFinder` in near future. Meanwhile, you still\ncan implement yours, and maybe submit it to pull request... :)\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fopportus%2Fobject-mapper","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fopportus%2Fobject-mapper","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fopportus%2Fobject-mapper/lists"}