Weave Code
Code Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Mapper Laravel Package

cmath10/mapper

A lightweight Laravel/PHP mapping utility to transform data between arrays and objects using configurable field mappings. Helps normalize payloads, rename keys, cast values, and build DTO-style structures with minimal boilerplate.

View on GitHub
Deep Wiki
Context7

Mapper

The library provides a utility for converting objects to each other (and arrays to objects) and transferring data between them by reusable map.

Basic example:

class ArticleInput
{
    public $title;

    public $text;

    public $author;
}

class Article
{
    public $title;

    public $text;

    public $author;

    public function __construct(?string $title = null)
    {
        $this->title = $title;
    }
}

$input = new ArticleInput();
$input->title = 'title';
$input->text = 'text';

$article1 = new Article();

$mapper = new cmath10\Mapper\Mapper();
$mapper->create(ArticleInput::class, Article::class);

$mapper->map($input, $article1);

// ($article1->title === 'title') === true
// ($article1->text === 'text') === true

$article2 = $mapper->map($input, Article::class)

// ($article2 instanceof Article) === true
// ($article2->title === 'title') === true
// ($article2->text === 'text') === true

How it works

The main term of the library is a map. It is an object which is containing information about how to extract values from the source and inject them into a destination object. Values can be processed before injection (to get an opportunity for sanitization and nested mapping).

Map includes:

  • field accessors — objects are responsible for value extraction; by default, it is used symfony/property-access for that purpose, but there are other options, like using callbacks or symfony/expression-language and presets (if the extracted value is null), also there is an interface for custom accessors;
  • field filters — objects are responsible for value processing; by default, there are no filters for extracted value; you can use callbacks and mapping filters for nested mapping.

By default, a map creates a set of accessors based on a class properties list, so a field from a source will be mapped on a field of a destination with the same name. You can change this behavior for specific fields if needed.

All maps must be registered in a mapper instance before using. When you call the mapper method map it seeks the correct map for a given source and destination, then it uses accessors to extract values, filters to process them, and, after that, injects values to a destination. If the destination is a class name, the mapper will try to create an instance of the given class, if the class constructor is public and all possible arguments are optional.

Installation

composer require cmath10/mapper

API

cmath10\Mapper\MapperInterface — interface for mappers:

  • create(string $sourceType, string $destinationType) — creates, registers and returns default map for two classes;
  • register(MapInterface $map) — registers a custom map in the mapper;
  • map($source, $destination) — performs mapping from a source to a destination object or class.

cmath10\mapper\MapInterface — interface for maps:

  • getSourceType() — returns name of source type; used to find the correct map;
  • getDestinationType() — returns name of destination type; used to find the correct map;
  • getFieldAccessors() — returns all fields accessors;
  • getFieldFilters() — returns all fields filters;
  • getOverwriteIfSet() — returns boolean flag; if true, destination member will be overwritten even if its value is not null;
  • getSkipNull() — returns boolean flag; if true, the mapper will not push null source member's value;
  • route(string $destinationMember, string $sourceMember) — sets mapping of destination route to source route; fluent;
  • forMember(string $destinationMember, AccessorInterface $fieldMapper) — sets custom accessor for field; fluent;
  • filter(string $destinationMember, FilterInterface $fieldFilter) — sets a custom filter for value processing; fluent;
  • ignoreMember(string $destinationMember) — excludes members from accounting, so they will not be filled; fluent.

cmath10\mapper\FieldAccessor\AccessorInterface — interface for accessors:

  • getValue($source) — extracts value from a source.

cmath10\mapper\FieldFilter\FitlerInterface — interface for filters:

  • filter($value) — processes the value.

cmath10\Mapper\TypeFilterInterface — interface for type filters is used to process type names. Useful for extracting class names from runtime generated classes like proxies in Doctrine. Used in default mapper which uses an array of type filters as a constructor parameter (to instantiate cmath10\mapper\TypeGuesser which is used to determine the correct map for supplied types in mapper's map method).

Available maps

AbstractMap

Basic class is used to create custom map classes, implements MapperInterface; provides following protected method:

  • setupDefaults — creates a set of accessors based on a class properties list.

Usage example:

use cmath10\Mapper\AbstractMap;

class ArticleOutputMap extends AbstractMap
{
    public function __construct()
    {
        $this
            ->setupDefaults() // create default accessors set
            ->route('textNotMappedByDefault', 'text') // customize
        ;
    }

    public function getSourceType(): string
    {
        return Article::class;
    }

    public function getDestinationType(): string
    {
        return ArticleOutput::class;
    }
}

DefaultMap

Simple descendant of AbstractMap, just calls setupDefaults in its constructor; the mapper create method creates, registers and returns instance of this class.

Available accessors

ClosureAccessor

Uses callback to extract a value:

use cmath10\Mapper\Mapper;
use cmath10\Mapper\FieldAccessor\ClosureAccessor;

$mapper = new Mapper();
$mapper
    ->create(Fixtures\Article::class, Fixtures\ArticleInput::class)
    ->forMember('author', new ClosureAccessor(fn (Fixtures\ArticleInput $a) => $a->author->name))
;

ExpressionAccessor

Uses symfony/expression-language to extract a value:

use cmath10\Mapper\Mapper;
use cmath10\Mapper\FieldAccessor\ExpressionAccessor;

$mapper = new Mapper();
$mapper
    ->create(Fixtures\MagazineWithPrivateProperties::class, Fixtures\MagazineOutput::class)
    ->forMember('articles', new ExpressionAccessor('getArticles()'))
;

PresetAccessor

Does not extract a value, just provides:

use cmath10\Mapper\Mapper;
use cmath10\Mapper\FieldAccessor\PresetAccessor;

$mapper = new Mapper();
$mapper
    ->create(Fixtures\MagazineWithPrivateProperties::class, Fixtures\MagazineOutput::class)
    ->forMember('articles', new PresetAccessor([]))
;

PropertyPathAccessor

Uses symfony/property-access to extract a value. This accessor is used by default. Calls like:

use cmath10\Mapper\Mapper;
use cmath10\Mapper\FieldAccessor\PropertyPathAccessor;

$mapper = new Mapper();
$mapper
    ->create(Fixtures\MagazineWithPrivateProperties::class, Fixtures\MagazineOutput::class)
    ->forMember('articles', new PropertyPathAccessor('someFieldWithArticles'))
;

and

use cmath10\Mapper\Mapper;
use cmath10\Mapper\FieldAccessor\PropertyPathAccessor;

$mapper = new Mapper();
$mapper
    ->create(Fixtures\MagazineWithPrivateProperties::class, Fixtures\MagazineOutput::class)
    ->route('articles', 'someFieldWithArticles')
;

are equivalent. Also, if the fields in a source and destination have the same name, and you use setupDefaults, you don't need to call route or forMember explicitly.

Available filters

ClosureFilter

Uses callback to process value:

use cmath10\Mapper\Mapper;
use cmath10\Mapper\FieldFilter\ClosureFilter;

// For ArticleInput title='title' we will get Article title='[[title]]'
$mapper = new Mapper();
$mapper
    ->create(Fixtures\ArticleInput::class, Fixtures\Article::class)
    ->filter('title', new ClosureFilter(static fn ($title) => '[[' . $title . ']]'))
;

IfNullFilter

Replaces the value if it is null:

use cmath10\Mapper\Mapper;
use cmath10\Mapper\FieldFilter\IfNullFilter;

// For ArticleInput title=null we will get Article title='defaultTitle'
$mapper = new Mapper();
$mapper
    ->create(Fixtures\ArticleInput::class, Fixtures\Article::class)
    ->filter('title', new IfNullFilter('defaultTitle'))
;

AbstractMappingFilter

Basic class for filters that can use mapper. Requires class name in the constructor. Used for nested mapping.

ObjectMappingFilter

Descendant of AbstractMappingFilter, uses class name and mapper to make an object from a source member:

use cmath10\Mapper\Mapper;
use cmath10\Mapper\FieldFilter\ObjectMappingFilter;

$mapper = new Mapper();
$mapper->create(Fixtures\AuthorInput::class, Fixtures\Author::class);
$mapper
    ->create(Fixtures\ArticleInput::class, Fixtures\Article::class)
    ->route('author', 'author')
    ->filter('author', new ObjectMappingFilter(Fixtures\Author::class))
;

ObjectArrayMappingFilter

Descendant of AbstractMappingFilter, uses class name and mapper to make an array of objects from a source member, if the source member's value is array (returns an empty array instead):

use cmath10\Mapper\Mapper;
use cmath10\Mapper\FieldFilter\ObjectArrayMappingFilter;

$mapper = new Mapper();
$mapper->create(Fixtures\Article::class, Fixtures\ArticleOutput::class);
$mapper
    ->create(Fixtures\Magazine::class, Fixtures\MagazineOutput::class)
    ->route('articles', 'articles')
    ->filter('articles', new ObjectArrayMappingFilter(Fixtures\ArticleOutput::class))
;
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
terminal42/code-quality-tools
codifyo/ts-generator-bundle
andydefer/laravel-cluster
testo/fiber
mintobit/jobqueue
a4sex/maintenance-bundle
a4sex/entity-date-update
a4sex/client-identifier
a4sex/base-utilites
a4sex/key-value-storage
a4sex/micro-status
chilldev/dependency-injection-extra
datinglibre/datinglibre-app-api
biberltd/corebundle
bricre/symfony-bundle-test
biberltd/logbundle
dominium/http-adapter-bundle
dominium/google-analytics
a4sex/auto-clean-entity
christhompsontldr/laravel-inky