PHPackages                             alecszaharia/simmap - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Utility &amp; Helpers](/categories/utility)
4. /
5. alecszaharia/simmap

ActiveLibrary[Utility &amp; Helpers](/categories/utility)

alecszaharia/simmap
===================

A symmetrical object mapper

1.0.0(5mo ago)019PHPPHP &gt;=8.1

Since Nov 16Pushed 5mo agoCompare

[ Source](https://github.com/alecszaharia/object-maper)[ Packagist](https://packagist.org/packages/alecszaharia/simmap)[ RSS](/packages/alecszaharia-simmap/feed)WikiDiscussions main Synced 1mo ago

READMEChangelog (2)Dependencies (3)Versions (3)Used By (0)

Simmap - Symmetrical Object Mapper
==================================

[](#simmap---symmetrical-object-mapper)

A powerful PHP object mapper library with attribute-based configuration for Symfony projects. Maps DTOs to Entities (or any other classes) with support for symmetrical bidirectional mapping and nested property paths.

IMPORTANT: This is an AI generated object mapper keep this in mind.

Features
--------

[](#features)

- **Attribute-based Configuration**: Use PHP 8.1+ attributes to define mappings
- **Explicit Opt-in**: Classes must be marked with `#[Mappable]` for type safety
- **Symmetrical Mapping**: Same metadata works in both directions (A→B and B→A)
- **Nested Properties**: Support for mapping to/from nested properties like `user.address.city`
- **Auto-mapping**: Automatically maps properties with matching names
- **Array Mapping**: Map collections with automatic element type conversion
- **PropertyAccess Integration**: Leverages Symfony's PropertyAccess component for robust property access
- **Type Safe**: Full PHP 8.1+ type hints and strict types

Requirements
------------

[](#requirements)

- PHP &gt;= 8.1
- Symfony PropertyAccess component ^6.0 or ^7.0

Installation
------------

[](#installation)

```
composer require alecszaharia/simmap
```

Basic Usage
-----------

[](#basic-usage)

### Auto-mapping (Properties with Same Names)

[](#auto-mapping-properties-with-same-names)

**Important**: Both source and target classes must be marked with `#[Mappable]` attribute.

```
use Alecszaharia\Simmap\Mapper;
use Alecszaharia\Simmap\Attribute\Mappable;

#[Mappable]
class UserDTO {
    public string $name;
    public string $email;
}

#[Mappable]
class UserEntity {
    public string $name;
    public string $email;
}

$dto = new UserDTO();
$dto->name = 'John Doe';
$dto->email = 'john@example.com';

$mapper = new Mapper();
$entity = $mapper->map($dto, UserEntity::class);
```

### Custom Property Mapping

[](#custom-property-mapping)

Use the `#[MapTo]` attribute to map properties with different names:

```
use Alecszaharia\Simmap\Attribute\MapTo;
use Alecszaharia\Simmap\Attribute\Mappable;
use Alecszaharia\Simmap\Mapper;

#[Mappable]
class ProductDTO {
    public string $productName;

    #[MapTo('quantity')]
    public int $stock;
}

#[Mappable]
class ProductEntity {
    public string $productName;
    public int $quantity;
}

$mapper = new Mapper();
$entity = $mapper->map($dto, ProductEntity::class);
```

### Symmetrical Mapping

[](#symmetrical-mapping)

The mapper works bidirectionally - you can map in reverse without additional configuration:

```
// Forward: DTO → Entity
$entity = $mapper->map($dto, ProductEntity::class);

// Reverse: Entity → DTO (uses the same mapping metadata)
$dto = $mapper->map($entity, ProductDTO::class);
```

### Nested Property Mapping

[](#nested-property-mapping)

Map flat properties to nested object structures:

```
use Alecszaharia\Simmap\Attribute\MapTo;
use Alecszaharia\Simmap\Attribute\Mappable;

#[Mappable]
class PersonDTO {
    public string $name;

    #[MapTo('address.city')]
    public string $city;

    #[MapTo('address.country')]
    public string $country;
}

#[Mappable]
class Address {
    public string $city;
    public string $country;
}

#[Mappable]
class PersonEntity {
    public string $name;
    public Address $address;

    public function __construct() {
        $this->address = new Address();
    }
}

$dto = new PersonDTO();
$dto->name = 'Jane Smith';
$dto->city = 'New York';
$dto->country = 'USA';

$entity = $mapper->map($dto, PersonEntity::class);
// $entity->address->city === 'New York'
// $entity->address->country === 'USA'
```

### Ignoring Properties

[](#ignoring-properties)

Use `#[Ignore]` to exclude properties from mapping:

```
use Alecszaharia\Simmap\Attribute\Ignore;
use Alecszaharia\Simmap\Attribute\Mappable;

#[Mappable]
class OrderDTO {
    public int $orderId;
    public float $total;

    #[Ignore]
    public string $tempData; // This won't be mapped
}
```

### Array Mapping

[](#array-mapping)

Use `#[MapArray]` to automatically map arrays of objects:

```
use Alecszaharia\Simmap\Attribute\MapArray;
use Alecszaharia\Simmap\Attribute\Mappable;

#[Mappable]
class OrderItemDTO {
    public string $productName;
    public int $quantity;
}

#[Mappable]
class OrderItem {
    public string $productName;
    public int $quantity;
}

#[Mappable]
class OrderDTO {
    public int $orderId;

    #[MapArray(OrderItem::class)]
    public array $items = [];
}

#[Mappable]
class Order {
    public int $orderId;

    #[MapArray(OrderItemDTO::class)]
    public array $items = [];
}

// Each item in the array is automatically mapped
$order = $mapper->map($orderDto, Order::class);
```

Features:

- **Automatic element mapping**: Each array element is recursively mapped to the target class
- **Preserves keys**: Works with both indexed and associative arrays
- **Symmetrical**: Works in both directions (bidirectional)
- **Can be combined with `#[MapTo]`**: Change both property name and element type

Advanced Usage
--------------

[](#advanced-usage)

### Mapping to Existing Objects

[](#mapping-to-existing-objects)

Instead of passing a class name, you can pass an existing object instance:

```
$existingEntity = new UserEntity();
$mapper->map($dto, $existingEntity);
```

### Symfony Integration

[](#symfony-integration)

The mapper can be registered as a service in Symfony:

```
# config/services.yaml
services:
    Alecszaharia\Simmap\Mapper:
        arguments:
            $propertyAccessor: '@property_accessor'
```

### Custom PropertyAccessor

[](#custom-propertyaccessor)

Inject a custom PropertyAccessor for advanced configuration:

```
use Symfony\Component\PropertyAccess\PropertyAccess;

$propertyAccessor = PropertyAccess::createPropertyAccessorBuilder()
    ->enableExceptionOnInvalidIndex()
    ->getPropertyAccessor();

$mapper = new Mapper($propertyAccessor);
```

Exception Handling
------------------

[](#exception-handling)

The mapper throws `MappingException` in the following scenarios:

### Invalid Target Type

[](#invalid-target-type)

```
use Alecszaharia\Simmap\Exception\MappingException;

try {
    $mapper->map($dto, 123); // Invalid: not an object or class name
} catch (MappingException $e) {
    // "Invalid target type "integer". Target must be an object instance, a class name string, or null."
}
```

### Cannot Create Instance

[](#cannot-create-instance)

```
interface UserInterface {}

try {
    $mapper->map($dto, UserInterface::class); // Cannot instantiate interface
} catch (MappingException $e) {
    // "Cannot create instance of class "UserInterface": Class is not instantiable (abstract or interface)"
}
```

### Property Access Errors

[](#property-access-errors)

```
class ReadOnlyEntity {
    private string $id;

    public function getId(): string {
        return $this->id;
    }
    // No setter - property is read-only
}

try {
    $mapper->map($dto, ReadOnlyEntity::class);
} catch (MappingException $e) {
    // "Cannot access property "id" on class "ReadOnlyEntity": ..."
}
```

### Graceful Handling

[](#graceful-handling)

The mapper gracefully handles:

- **Uninitialized properties**: Skipped during mapping (no exception)
- **Missing properties**: Auto-skipped if no mapping defined
- **Inaccessible properties**: Read errors are silently skipped; write errors throw exception

```
class PartialDTO {
    public string $name; // Not initialized
    public string $email = 'test@example.com';
}

$dto = new PartialDTO();
// $dto->name is uninitialized - will be skipped
$entity = $mapper->map($dto, UserEntity::class);
// Only 'email' is mapped, 'name' is skipped without error
```

How It Works
------------

[](#how-it-works)

1. **Metadata Extraction**: The mapper uses PHP Reflection to read attributes from source and target classes
2. **Property Resolution**: For each source property, it determines the target property path by:
    - Checking for explicit `#[MapTo]` attribute on source
    - Checking for reverse mapping in target metadata (symmetrical mapping)
    - Auto-mapping if property names match and target has the property
3. **Value Transfer**: Uses Symfony's PropertyAccess component to read from source and write to target, supporting nested paths

Testing
-------

[](#testing)

The project includes a Makefile for running tests:

```
# Run all tests
make test

# Run specific test file
make test-file FILE=tests/Unit/MapperTest.php

# Run specific test method
make test-filter FILTER=testMethodName

# Run with coverage report
make test-coverage

# Run performance benchmarks
make benchmark

# Show all available commands
make help
```

See [CONTRIBUTING.md](CONTRIBUTING.md) for more details on development workflow.

API Reference
-------------

[](#api-reference)

### Mapper

[](#mapper)

```
public function map(object $source, object|string|null $target = null): object
```

- `$source`: Source object to map from
- `$target`: Target object instance, class name string, or null
- Returns: The mapped target object

### Attributes

[](#attributes)

#### `#[Mappable]`

[](#mappable)

**Required** - Marks a class as eligible for object mapping. Both source and target classes must have this attribute.

```
use Alecszaharia\Simmap\Attribute\Mappable;

#[Mappable]
class UserDTO {
    public string $name;
}
```

Without this attribute, attempting to map will throw:

```
MappingException: Class "UserDTO" cannot be used as source for mapping.
Add #[Mappable] attribute to the class to enable mapping.

```

#### `#[MapTo(string $targetProperty)]`

[](#maptostring-targetproperty)

Maps a property to a different property path in the target object.

```
#[MapTo('user.profile.displayName')]
public string $name;
```

#### `#[Ignore]`

[](#ignore)

Excludes a property from automatic mapping.

```
#[Ignore]
public string $internalData;
```

#### `#[MapArray(class-string $targetClass)]`

[](#maparrayclass-string-targetclass)

Maps an array property with automatic element type conversion.

```
#[MapArray(OrderItem::class)]
public array $items = [];
```

Can be combined with `#[MapTo]`:

```
#[MapArray(OrderItem::class)]
#[MapTo('orderItems')]
public array $items = [];
```

Examples
--------

[](#examples)

See the `examples/BasicUsage.php` file for comprehensive examples covering all features.

For advanced examples and real-world use cases, check the [Examples Guide](docs/examples.md).

Documentation
-------------

[](#documentation)

Comprehensive documentation is available in the `docs/` directory:

- **[Architecture](docs/architecture.md)** - Internal design and implementation details
- **[Performance](docs/performance.md)** - Benchmarking and optimization guide
- **[Troubleshooting](docs/troubleshooting.md)** - Common issues and solutions
- **[Examples](docs/examples.md)** - Advanced use cases and patterns

See the [Documentation Index](docs/README.md) for the complete guide.

License
-------

[](#license)

MIT

Contributing
------------

[](#contributing)

Contributions are welcome! Please submit pull requests with tests.

###  Health Score

34

—

LowBetter than 77% of packages

Maintenance70

Regular maintenance activity

Popularity7

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity45

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Unknown

Total

1

Last Release

177d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/1c1909ac82bf0f7fa1e7c2b1e4bf902a51161b36ea7f1ba093e9774c417cf761?d=identicon)[alecszaharia](/maintainers/alecszaharia)

---

Top Contributors

[![alecszaharia](https://avatars.githubusercontent.com/u/3128048?v=4)](https://github.com/alecszaharia "alecszaharia (11 commits)")

###  Code Quality

TestsPHPUnit

### Embed Badge

![Health badge](/badges/alecszaharia-simmap/health.svg)

```
[![Health](https://phpackages.com/badges/alecszaharia-simmap/health.svg)](https://phpackages.com/packages/alecszaharia-simmap)
```

###  Alternatives

[winzou/state-machine

A very lightweight yet powerful PHP state machine

52113.7M18](/packages/winzou-state-machine)[hirethunk/verbs

An event sourcing package that feels nice.

513162.9k6](/packages/hirethunk-verbs)[cognesy/instructor-php

The complete AI toolkit for PHP: unified LLM API, structured outputs, agents, and coding agent control

310107.9k1](/packages/cognesy-instructor-php)[symfony/ai-platform

PHP library for interacting with AI platform provider.

51927.7k136](/packages/symfony-ai-platform)[solspace/craft-freeform

The most flexible and user-friendly form building plugin!

52664.9k12](/packages/solspace-craft-freeform)[codefog/contao-haste

haste extension for Contao Open Source CMS

42650.8k139](/packages/codefog-contao-haste)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
