{"id":18953281,"url":"https://github.com/symfonycasts/micro-mapper","last_synced_at":"2025-04-12T20:44:15.998Z","repository":{"id":189163119,"uuid":"680180252","full_name":"SymfonyCasts/micro-mapper","owner":"SymfonyCasts","description":"A tiny, underwhelming data mapper for Symfony to map one object to another!","archived":false,"fork":false,"pushed_at":"2024-11-28T14:28:53.000Z","size":60,"stargazers_count":70,"open_issues_count":5,"forks_count":5,"subscribers_count":9,"default_branch":"main","last_synced_at":"2025-04-11T22:59:14.930Z","etag":null,"topics":["data-mapper","data-mapping","mapper","symfony"],"latest_commit_sha":null,"homepage":"https://symfonycasts.com","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/SymfonyCasts.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-08-18T14:33:45.000Z","updated_at":"2025-03-21T09:25:05.000Z","dependencies_parsed_at":null,"dependency_job_id":"905f0453-d9eb-47cc-be96-5480100b8c15","html_url":"https://github.com/SymfonyCasts/micro-mapper","commit_stats":null,"previous_names":["symfonycasts/micro-mapper"],"tags_count":7,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SymfonyCasts%2Fmicro-mapper","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SymfonyCasts%2Fmicro-mapper/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SymfonyCasts%2Fmicro-mapper/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SymfonyCasts%2Fmicro-mapper/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/SymfonyCasts","download_url":"https://codeload.github.com/SymfonyCasts/micro-mapper/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248631687,"owners_count":21136556,"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":["data-mapper","data-mapping","mapper","symfony"],"created_at":"2024-11-08T13:37:50.686Z","updated_at":"2025-04-12T20:44:15.975Z","avatar_url":"https://github.com/SymfonyCasts.png","language":"PHP","readme":"# MicroMapper: The Tiny, Underwhelming Data Mapper for Symfony!\n\n[![CI](https://github.com/SymfonyCasts/micro-mapper/actions/workflows/ci.yaml/badge.svg)](https://github.com/SymfonyCasts/micro-mapper/actions/workflows/ci.yaml)\n\nNeed to map one object (e.g. a Doctrine entity) to another\nobject (e.g. a DTO) and *love* writing the mapping code manually?\nThen this library is for you!\n\nDefine a \"mapper\" class:\n\n```php\nuse App\\Entity\\Dragon;\nuse App\\DTO\\DragonDTO;\n\n#[AsMapper(from: Dragon::class, to: DragonDTO::class)]\nclass DragonEntityToDtoMapper implements MapperInterface\n{\n    public function load(object $from, string $toClass, array $context): object\n    {\n        $entity = $from;\n\n        return new DragonDTO($entity-\u003egetId());\n    }\n\n    public function populate(object $from, object $to, array $context): object\n    {\n        $entity = $from;\n        $dto = $to;\n\n        $dto-\u003ename = $entity-\u003egetName();\n        $dto-\u003efirePower = $entity-\u003egetFirePower();\n\n        return $dto;\n    }\n}\n```\n\nThen... map!\n\n```php\n$dragon = $dragonRepository-\u003efind(1);\n$dragonDTO = $microMapper-\u003emap($dragon, DragonDTO::class);\n```\n\nMicroMapper is similar to other data mappers, like\n[jolicode/automapper](https://github.com/jolicode/automapper), except... less\nimpressive! Jane's Automapper is awesome and handles a lot of heavy lifting.\nWith MicroMapper, *you* do the heavy lifting. Let's review with a table!\n\n| Feature                          | MicroMapper | Jane's Automapper |\n|----------------------------------|-------------|-------------------|\n| Some of the mapping is automatic | ❌           | ✅                 |\n| Extensible                       | ✅           | ✅                 |\n| Handles nested objects           | ✅           | ✅                 |\n| Small \u0026 Dead-simple              | ✅           | (not SO simple)   |\n\n## Support us \u0026 Symfony\n\nIs this package useful! We're *thrilled* 😍!\n\nA lot of time \u0026 effort from the Symfonycasts team \u0026 the Symfony community\ngoes into creating and maintaining these packages. You can support us +\nSymfony (and learn a bucket-load) by grabbing a subscription to [SymfonyCasts](https://symfonycasts.com)!\n\n## Installation\n\n```bash\ncomposer require symfonycasts/micro-mapper\n```\n\nIf you're using Symfony, you're done! If not, see\n[Stand-alone Library Setup](#stand-alone-library-setup).\n\n## Usage\n\nSuppose you have a `Dragon` entity, and you want to map it to a\n`DragonApi` object (perhaps to use with API Platform, like we do\nin our [Api Platform EP3 Tutorial](https://symfonycasts.com/screencast/api-platform3-extending)).\n\n### Step 1: Create the Mapper Class\n\nTo do this, create a \"mapper\" class that defines how to map:\n\n```php\nnamespace App\\Mapper;\n\nuse App\\Entity\\Dragon;\nuse App\\ApiResource\\DragonApi;\nuse Symfonycasts\\MicroMapper\\AsMapper;\nuse Symfonycasts\\MicroMapper\\MapperInterface;\n\n#[AsMapper(from: Dragon::class, to: DragonApi::class)]\nclass DragonEntityToApiMapper implements MapperInterface\n{\n    public function load(object $from, string $toClass, array $context): object\n    {\n        $entity = $from;\n        assert($entity instanceof Dragon); // helps your editor know the type\n\n        return new DragonApi($entity-\u003egetId());\n    }\n\n    public function populate(object $from, object $to, array $context): object\n    {\n        $entity = $from;\n        $dto = $to;\n        // helps your editor know the types\n        assert($entity instanceof Dragon);\n        assert($dto instanceof DragonApi);\n\n        $dto-\u003ename = $entity-\u003egetName();\n        $dto-\u003efirePower = $entity-\u003egetFirePower();\n\n        return $dto;\n    }\n}\n```\n\nThe mapper class has three parts:\n\n1. `#[AsMapper]` attribute: defines the \"from\" and \"to\" classes (needed for\n   Symfony usage only).\n2. `load()` method: creates/loads the \"to\" object - e.g. load it from the\n    database or create it and populate just the identifier.\n3. `populate()` method: populates the \"to\" object with data from the \"from\"\n    object.\n\n### Step 2: Use the MicroMapper Service\n\nTo use the mapper, you can fetch the `MicroMapperInterface` service. For\nexample, from a controller:\n\n```php\n\u003c?php\n\nnamespace App\\Controller;\n\nuse App\\Entity\\Dragon;\nuse App\\ApiResource\\DragonApi;\nuse Symfonycasts\\MicroMapper\\MicroMapperInterface;\nuse Symfony\\Bundle\\FrameworkBundle\\Controller\\AbstractController;\nuse Symfony\\Component\\Routing\\Annotation\\Route;\n\nclass DragonController extends AbstractController\n{\n    #[Route('/dragons/{id}', name: 'api_dragon_get_collection')]\n    public function index(Dragon $dragon, MicroMapperInterface $microMapper)\n    {\n        $dragonApi = $microMapper-\u003emap($dragon, DragonApi::class);\n\n        return $this-\u003ejson($dragonApi);\n    }\n}\n```\n\n## Reverse Transforming\n\nTo do the reverse transformation - `DragonApi` to `Dragon` - it's\nthe same process: create a mapper class:\n\nThe mapper:\n\n```php\nnamespace App\\Mapper;\n\nuse App\\ApiResource\\DragonApi;\nuse App\\Entity\\Dragon;\nuse App\\Repository\\DragonRepository;\nuse Symfonycasts\\MicroMapper\\AsMapper;\nuse Symfonycasts\\MicroMapper\\MapperInterface;\n\n#[AsMapper(from: DragonApi::class, to: Dragon::class)]\nclass DragonApiToEntityMapper implements MapperInterface\n{\n    public function __construct(private DragonRepository $dragonRepository)\n    {\n    }\n\n    public function load(object $from, string $toClass, array $context): object\n    {\n        $dto = $from;\n        assert($dto instanceof DragonApi);\n\n        return $dto-\u003eid ? $this-\u003edragonRepository-\u003efind($dto-\u003eid) : new Dragon();\n    }\n\n    public function populate(object $from, object $to, array $context): object\n    {\n        $dto = $from;\n        $entity = $to;\n        assert($dto instanceof DragonApi);\n        assert($entity instanceof Dragon);\n\n        $entity-\u003esetName($dto-\u003ename);\n        $entity-\u003esetFirePower($dto-\u003efirePower);\n\n        return $entity;\n    }\n}\n```\n\nIn this case, the `load()` method fetches the `Dragon` entity from the\ndatabase if it has an `id` property.\n\n## Handling Nested Objects\n\nIf you have nested objects, you can use the `MicroMapperInterface` to map\nthose too. Suppose the `Dragon` entity has a `treasures` property\nthat is a `OneToMany` relation to `Treasure` entity. And in `DragonApi`, we have\na `treasures` property that should hold an array of `TreasureApi` objects.\n\nFirst, create a mapper for the `Treasure` -\u003e `TreasureApi` mapping:\n\n```php\n// ...\n\n#[AsMapper(from: Treasure::class, to: TreasureApi::class)]\nclass TreasureEntityToApiMapper implements MapperInterface\n{\n    public function load(object $from, string $toClass, array $context): object\n    {\n        return new TreasureApi($from-\u003egetId());\n    }\n\n    public function populate(object $from, object $to, array $context): object\n    {\n        $entity = $from;\n        $dto = $to;\n\n        // ... map all the properties\n\n        return $dto;\n    }\n}\n```\n\nNext, in the `DragonEntityToApiMapper`, use the `MicroMapperInterface` to map the\n`Treasure` objects to `TreasureApi` objects:\n\n```php\nnamespace App\\Mapper;\n\n// ...\nuse App\\ApiResource\\TreasureApi;\nuse Symfonycasts\\MicroMapper\\MicroMapperInterface;\n\n#[AsMapper(from: Dragon::class, to: DragonApi::class)]\nclass DragonEntityToApiMapper implements MapperInterface\n{\n    public function __construct(private MicroMapperInterface $microMapper)\n    {\n    }\n\n    // load() is the same\n\n    public function populate(object $from, object $to, array $context): object\n    {\n        $entity = $from;\n        $dto = $to;\n        // ... other properties\n\n        $treasuresApis = [];\n        foreach ($entity-\u003egetTreasures() as $treasureEntity) {\n            $treasuresApis[] = $this-\u003emicroMapper-\u003emap($treasureEntity, TreasureApi::class, [\n                MicroMapperInterface::MAX_DEPTH =\u003e 1,\n            ]);\n        }\n        $dto-\u003etreasures = $treasuresApis;\n\n        return $dto;\n    }\n}\n```\n\nThat's it! The result will be a `DragonApi` object with a `treasures` property\nthat holds an array of `TreasureApi` objects.\n\n## MAX_DEPTH \u0026 Circular References\n\nImagine now that `TreasureEntityToApiMapper` *also* maps a `dragon`\nproperty on the `TreasureApi` object:\n\n```php\n// ...\n\n#[AsMapper(from: Treasure::class, to: TreasureApi::class)]\nclass TreasureEntityToApiMapper implements MapperInterface\n{\n    public function __construct(private MicroMapperInterface $microMapper)\n    {\n    }\n\n    // load()\n\n    public function populate(object $from, object $to, array $context): object\n    {\n        $entity = $from;\n        $dto = $to;\n        // ... map all the properties\n        \n        $dto-\u003edragon = $this-\u003emicroMapper-\u003emap($entity-\u003egetDragon(), DragonApi::class, [\n            MicroMapperInterface::MAX_DEPTH =\u003e 1,\n        ]);\n\n        return $dto;\n    }\n}\n```\n\nThis creates a circular reference: the `Dragon` entity is mapped to a\n`DragonApi` object... which then maps its `treasures` property to an array\nof `TreasureApi` objects... which then each map their `dragon` property to a\n`DragonApi` object... forever... and ever... and ever...\n\nThe `MAX_DEPTH` option tells MicroMapper how many levels deep to\ngo when mapping, and you *usually* want to set this to 0 or 1 when mapping a\nrelation.\n\nWhen the max depth is hit, the `load()` method will be called on the mapper\nfor that level but `populate()` will *not* be called. This results in a\n\"shallow\" mapping of the final level object.\n\nLet's look at a few depth examples using this code:\n\n```php\n$dto-\u003edragon = $this-\u003emicroMapper-\u003emap($dragonEntity, DragonApi::class, [\n    MicroMapperInterface::MAX_DEPTH =\u003e ???,\n]);\n```\n\n* `MAX_DEPTH = 0`: Because the depth is immediately hit, the `Dragon` entity\n  will be mapped to a `DragonApi` object by calling the `load()` method on\n  `DragonEntityToApiMapper`. But the `populate()` method will *not* be called.\n  This means that the final `DragonApi` object will have an `id` but no other data.\n\nResult:\n\n```yaml\nDragonApi:\n    id: 1\n    name: null\n    firePower: null\n    treasures: []\n```\n\n* `MAX_DEPTH = 1`: The `Dragon` entity will be *fully* mapped to a\n  `DragonApi` object: both the `load()` and `populate()` methods will be\n  called on its mapper like normal. However, when each `Treasure` in\n  `Dragon.treasures` is mapped to a `TreasureApi` object, this will be\n  \"shallow\": the `TreasureApi` object will have an `id` property but\n  no other data (because the max depth was hit and so only `load()` is called\n  on `TreasureEntityToApiMapper`).\n\nResult:\n\n```yaml\nDragonApi:\n    id: 1\n    name: 'Sizzley Pete'\n    firePower: 100\n    treasures:\n        TreasureApi:\n            id: 1\n            name: null\n            value: null\n            dragon: null\n        TreasureApi:\n            id: 2\n            name: null\n            value: null\n            dragon: null\n```\n\nIn something like API Platform, you can also use `MAX_DEPTH` to limit the\ndepth of the serialization for performance. For example, if the `TreasureApi`\nobject has a `dragon` property that is expressed as the IRI string (e.g.\n`/api/dragons/1`), then setting `MAX_DEPTH` to `0` is enough and prevents\nextra mapping work.\n\n## Settable Collection Relations on Entities\n\nIn our example, the `Dragon` entity has a `treasures` property that is a\n`OneToMany` relation to the `Treasure` entity. Our DTO classes have\nthe same relation: `DragonApi` holds an array of `TreasureApi` objects.\nThose greedy dragons!\n\nIf you want to map a `DragonApi` object to the `Dragon` entity and\nthe `DragonApi.treasures` property may have changed, you need to\nupdate the `Dragon.treasures` properly carefully.\n\nFor example, this will **not work**:\n\n```php\n\n// ...\n\n#[AsMapper(from: DragonApi::class, to: Dragon::class)]\nclass DragonApiToEntityMapper implements MapperInterface\n{\n    // ...\n\n    public function populate(object $from, object $to, array $context): object\n    {\n        $dto = $from;\n        $entity = $to;\n        // ...\n\n        $treasureEntities = new ArrayCollection();\n        foreach ($dto-\u003etreasures as $treasureApi) {\n            $treasureEntities[] = $this-\u003emicroMapper-\u003emap($treasureApi, Treasure::class, [\n                // depth=0 because we really just need to load/query each Treasure entity\n                MicroMapperInterface::MAX_DEPTH =\u003e 0,\n            ]);\n        }\n\n        // !!!!! THIS WILL NOT WORK !!!!!\n        $entity-\u003esetTreasures($treasureEntities);\n\n        return $entity;\n    }\n}\n```\n\nThe problem is with the `$entity-\u003esetTreasures()` call. In fact, this method probably\ndoesn't even exist on the `Dragon` entity! Instead, it likely has `addTreasure()` and\n`removeTreasure()` methods and *these* must be called instead so that the \"owning\"\nside of the Doctrine relationship is correctly set (otherwise the changes won't save).\n\nAn easy way to do this is with the `PropertyAccessorInterface` service:\n\n```php\n\n// ...\nuse Symfony\\Component\\PropertyAccess\\PropertyAccessorInterface;\n\n#[AsMapper(from: DragonApi::class, to: Dragon::class)]\nclass DragonApiToEntityMapper implements MapperInterface\n{\n    public function __construct(\n        private MicroMapperInterface $microMapper,\n        private PropertyAccessorInterface $propertyAccessor\n    )\n    {\n    }\n\n    // ...\n\n    public function populate(object $from, object $to, array $context): object\n    {\n        $dto = $from;\n        $entity = $to;\n        // ...\n\n        $treasureEntities = [];\n        foreach ($dto-\u003etreasures as $treasureApi) {\n            $treasureEntities[] = $this-\u003emicroMapper-\u003emap($treasureApi, Treasure::class, [\n                MicroMapperInterface::MAX_DEPTH =\u003e 0,\n            ]);\n        }\n\n        // this will call the addTreasure() and removeTreasure() methods\n        $this-\u003epropertyAccessor-\u003esetValue($entity, 'treasures', $treasureEntities);\n\n        return $entity;\n    }\n}\n```\n\n## Standalone Library Setup\n\nIf you're not using Symfony, you can still use MicroMapper! You'll need to\ninstantiate the `MicroMapper` class and pass it all of your mappings:\n\n```php\n$microMapper = new MicroMapper([]);\n$microMapper-\u003eaddMapperConfig(new MapperConfig(\n    from: Dragon::class,\n    to: DragonApi::class,\n    fn() =\u003e new DragonEntityToApiMapper($microMapper)\n));\n$microMapper-\u003eaddMapperConfig(new MapperConfig(\n    from: DragonApi::class,\n    to: Dragon::class,\n    fn() =\u003e new DragonApiToEntityMapper($microMapper)\n));\n\n// now it's ready to use!\n```\n\nIn this case, the `#[AsMapper]` attribute is not needed.\n\n## Credits\n\n- [Ryan Weaver](https://github.com/weaverryan)\n- [All Contributors](../../contributors)\n\n## License\n\nMIT License (MIT): see the [License File](LICENSE.md) for more details.\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsymfonycasts%2Fmicro-mapper","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsymfonycasts%2Fmicro-mapper","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsymfonycasts%2Fmicro-mapper/lists"}