Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Map from flat datas to structured datas #201

Open
dsoriano opened this issue Oct 23, 2024 · 2 comments
Open

Map from flat datas to structured datas #201

dsoriano opened this issue Oct 23, 2024 · 2 comments
Labels
question Further information is requested

Comments

@dsoriano
Copy link

Hi all,

This is my use case :

class Source
{
    #[MapTo(target: 'Target', groups: ['write'])]
    public string $name;

    public string $street;
    public string $zip;
    public string $city;
    public string $country;
}

class TargetAddress {
    public string $street;
    public string $zip;
    public string $city;
    public string $country;
}

class Target
{
    public string $name;
    public ?TargetAddress $address = null;
}

$target = $automapper->map($source, new Target(), ['groups' => ['write']]);

I have to map the address fields from Source into an object in Target. There is a limitation : the target class can't be modified beause it comes from an external lib.

First I thought to do that :

class Source
{
    #[MapTo(target: 'Target', groups: ['write'])]
    public string $name;

    public string $street;
    public string $zip;
    public string $city;
    public string $country;

    #[MapTo(target: 'Target', property: 'address', groups: ['write'])]
    public function getAddress(): array
    {
        return [
            'street' => $this->street,
            'zip' => $this->zip,
            'city' => $this->city,
            'country' => $this->country,
        ];
    }
}

But I doesn't work, It can convert the array to a new TargetAddress instance.

Then I tried :

class Source
{
    #[MapTo(target: 'Target', groups: ['write'])]
    public string $name;

    public string $street;
    public string $zip;
    public string $city;
    public string $country;

    #[MapTo(target: 'Target', property: 'address', groups: ['write'])]
    public function getAddress(): \stdClass
    {
        return (object)[
            'street' => $this->street,
            'zip' => $this->zip,
            'city' => $this->city,
            'country' => $this->country,
        ];
    }
}

Here the TargetAddress is created, but because of the group, the properties are not mapped.

Is there a way to do that simply ?

Thanks in advance

@Korbeil
Copy link
Member

Korbeil commented Oct 25, 2024

Hey @dsoriano and thanks for using AutoMapper !

After some tests, you should use the transformer on a class attribute as following:

#[MapTo(target: Target::class, property: 'address', transformer: 'getAddress', groups: ['write'])]
class Source
{
    #[MapTo(target: Target::class, groups: ['write'])]
    public string $name;

    public string $street;
    public string $zip;
    public string $city;
    public string $country;

    public function getAddress(): TargetAddress
    {
        $targetAddress = new TargetAddress();
        $targetAddress->street = $this->street;
        $targetAddress->zip = $this->zip;
        $targetAddress->city = $this->city;
        $targetAddress->country = $this->country;

        return $targetAddress;
    }
}

To explain a bit: MapTo attributes can only be placed onto properties or onto the class, so in your examples the MapTo attribute on getAddress was being ignored 😉
You need to make it a class attribute that use the method as transformer so it works well 👌

This should fix your issue ! Tell me if you have anything else you wanna ask otherwise feel free to close the issue.
Regards,
Baptiste

@Korbeil Korbeil added the question Further information is requested label Oct 25, 2024
@dsoriano
Copy link
Author

Hi @Korbeil
Thanks for your answer.

I'm pretty sure that the attribute MapTo on the method is not the problem and works. This code is working well :

class Source
{
    #[MapTo(target: 'Target')]
    public string $name;

    public string $street;
    public string $zip;
    public string $city;
    public string $country;

    #[MapTo(target: 'Target', property: 'address')]
    public function getAddress(): \stdClass
    {
        return (object)[
            'street' => $this->street,
            'zip' => $this->zip,
            'city' => $this->city,
            'country' => $this->country,
        ];
    }
}

class TargetAddress {
    public string $street;
    public string $zip;
    public string $city;
    public string $country;
}

class Target
{
    public string $name;
    public ?TargetAddress $address = null;
}

$automapper = AutoMapper::create();

$source = new Source();
$source->name = 'John Doe';
$source->street = 'Main Street 123';
$source->zip = '12345';
$source->city = 'Springfield';
$source->country = 'USA';

$target = $automapper->map($source, new Target());

The dump :
2024-10-27 11_36_33-Firefox Developer Edition

The problem is coming when I add the serialization group. Event this code doesn't work :

    #[MapTo(target: 'Target', property: 'address', groups: ['write'])]
    public function getAddress(): TargetAddress
    {
        $address = new TargetAddress();
        $address->street = $this->street;
        $address->zip = $this->zip;
        $address->city = $this->city;
        $address->country = $this->country;
        return $address;
    }

Before your suggestion, the only workaround I found is to create another class SourceAddress like this:

class SourceAddress {
    #[MapTo(target: 'TargetAddress', groups: ['write'])]
    public string $street;

    #[MapTo(target: 'TargetAddress', groups: ['write'])]
    public string $zip;

    #[MapTo(target: 'TargetAddress', groups: ['write'])]
    public string $city;

    #[MapTo(target: 'TargetAddress', groups: ['write'])]
    public string $country;
}

And my method getAddress :

    #[MapTo(target: 'Target', property: 'address', groups: ['write'])]
    public function getAddress(): SourceAddress
    {
        $address = new SourceAddress();
        $address->street = $this->street;
        $address->zip = $this->zip;
        $address->city = $this->city;
        $address->country = $this->country;
        return $address;
    }

It seems confirm that this is a serialization group problem. But by using a new SourceAddress class and populate it manually for the mapping, I lost a little bit the benefits of an automapping.

Your code is working well also, but the problem is the transformer must return a TargetAddress class. This is restrictive if I want to use the same transformer for another target, and here again I lots the benefits of automapping if I have to populate a part of the target class manually.

Thank you

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question Further information is requested
Projects
None yet
Development

No branches or pull requests

2 participants