Git Product home page Git Product logo

automapper's People

Contributors

adrienbrault avatar jmsche avatar joelwurtz avatar korbeil avatar nikophil avatar priyadi avatar soyuka avatar thepanz avatar welcomattic avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

automapper's Issues

Default values in target are not handled

This PR made me realize there is another problem, a little bit more complex to mitigate:

readonly class SomeClass
{
    public function __construct(public int|null $foo = 1, public int $bar = 0)
    {
    }
}

$autoMapper->map([], SomeClass::class);

two problems here:

  • foo defaults to null whereas it should default to 1
  • the above code creates an error because key bar is not present, although the property has a default value

I'm not sure how we could fix this... maybe by adding the default value in the WriteMutator?

bug: problem when getter type is different from property type

Description
when using #[MapToAttribute], when the getter type is different from the property type, the automapper guesses the type from the property, although it should guess it from the getter's return type

How to reproduce

class SomeClass
{
	public function __construct(private SomeEnum $enum){}

    public function getEnum(#[MapToAttribute('foo) string $foo]): string
    {
		return "{$foo}-{$this->enum->value}";
	}
}

$automapper->map(new SomeClass(SomeEnum::FOO, 'array'));

this code produces an error, because the generated mapper thinks SomeClass::getEnum() will return an enum

Possible Solution
don't know yet, need to dig into the generator

Flattening

Inspired from https://docs.automapper.org/en/stable/Flattening.html

The idea is to makes simpler DTO compared to your source entities (or whatever your source is).
Let's do a quick example:

// source
class User
{
  public string $email;
}

class Order
{
  public string $reference;
  public User $customer;
}

// target
class OrderDTO
{
  public string $reference;
  public string $customerEmail;
}

$customer = new User();
$customer->email = '[email protected]';
$source = new Order();
$source->reference = 'FAC9123';
$source->customer = $customer;

dump($autoMapper->map($source, OrderDTO::class));

// Will output:
//
// OrderDTO {#14 ๐Ÿ”ฝ
//  +reference: "FAC9123"
//  +customerEmail: "[email protected]"
// }

Idea is to flatten the objects so they can be mapped to a property with a name matching their path, in this example we have "Order -> customer (User) -> email" that is flatten to "Order -> customerEmail"

Private properties in constructor are not mapped

Original issue: janephp/janephp#749
Reported by: @nikophil

Jane version(s) affected: 7.5.3

Description
a private property in the constructor without any getter/setter will not be mapped

How to reproduce
The following code will create an error, because the generated mapper will be empty

class SomeClass
{
    public function __construct(
        private string $someProp
    ){}
}

$automapper->map(['someProp' => 'foo'], SomeClass::class);

Possible Solution
dig in the generator and allow this kind of props

Add AutoMapper version within Mappers hash to force hot-reload

Today we have mappers that are generated based on their metadata. For each mapper we make a hash that is used to check if we need to re-generate it or not. This behavior is called hot-reload. It's enabled by default because in dev you want that feature.

Depending on the AutoMapper version, you probably have some changes on the mappers and want them to be re-generated that's why we think it would be nice to have the AutoMapper version within the generated hash so when the library version changes, it will force a mapper generation (see #11 (comment)).

Handle non-initialized properties

When generating a Mapper, we will have stuff like this: \AutoMapper\MapperContext::isAllowedAttribute($context, 'scoreOnCreation', $fieldvalue_20 = $value->scoreOnCreation).
But if the field is not initialized before it will fail violently. We should handle non-initiliazed properties a better way, isset can tell if a property is initialized or not.

Split source array into differents target attributes

Is it possible to transform an object/DTO to multiple target attributes ?

Source:

{
    "name": "John DOE",
    "job": {
        "company": "MyCompany",
        "title": "I am a developer" 
    }
}
// MainDTO
final class ProfileDTO
{
   public string $name;

   #[MapTo(name: '????', transformer: JobTransformer::class)]
   #[ApiProperty(description: 'Job information.')]
   public ?JobDTO $job = null;
}
// Sub DTO
final class JobDTO
{
    #[ApiProperty(description: 'Company name.')]
    public ?string $company = null;

    public ?string $title = null;
}
// My entity
class Profile
{
   #[ORM\Column(length: 255, nullable: true)]
    private ?string $name = null;   

    #[ORM\Column(length: 255, nullable: true)]
    private ?string $jobTitle = null;

    #[ORM\Column(length: 255, nullable: true)]
    private ?string $jobCompany = null;
}
class JobTransformer implements PropertyTransformerInterface
{
    /**
     * @param JobDTO $value
     */
    public function transform(mixed $value, array|object $source, array $context): mixed
    {
        // ???
    }
}

Improve non-handled native PHP types

If we're using some native PHP type that wasn't handled in one of the current transformers, the error is not very explicit.
Tried recently with stuff like \DateInterval or \Generate and it failed violently.

Idea: try to improve errors so it's more explicit on what is the error and how to fix it.
Second time: add more native PHP stuff in the base AutoMapper transformers.

[AutoMapper] `MappingExtractor::getWriteMutator()` returns null if property is writable

Original issue: janephp/janephp#666
Reported by: @ihmels

Jane version(s) affected: 7.3.1

Description
If no WriteMutator can be determined via MappingExtractor::getWriteMutator(), but the property is writable according to PropertyInfoExtractorInterface::isWriteable(), an error occurs in line 73 of SourceTargetMappingExtractor, because $targetMutatorConstruct is not checked for null.

How to reproduce

In the following code snippet there is the property Attribute::$values and the corresponding setter Attribute::setValues() (actually this should be called "setValue", but I made a typo).

The PropertyInfoExtractorInterface finds out with the ReflectionExtractor that AttributeValues can be added with addValues(), so PropertyInfoExtractorInterface:isWriteable() returns true, but MappingExtractor::getWriteMutator() return null.

class Attribute
{
    private Collection $values;

    public function __construct()
    {
        $this->values = new ArrayCollection();
    }

    /**
     * @return Collection<int, AttributeValue>
     */
    public function getValues(): Collection
    {
        return $this->values;
    }

    public function addValues(AttributeValue $values): void
    {
        if (!$this->values->contains($values)) {
            $this->values->add($values);
            $values->setAttribute($this);
        }
    }

    public function removeValues(AttributeValue $values): void
    {
        if ($this->values->removeElement($values)) {
            // set the owning side to null (unless already changed)
            if ($values->getAttribute() === $this) {
                $values->setAttribute(null);
            }
        }
    }
}

class AttributeValue {
    // ...
}

Unable to install 8.2.2

Symfony 7
Php 8.2

Today, I attempted to test the package but encountered difficulty installing version 8.2.2.

When I executed composer require jolicode/automapper-bundle, it installed version 8.1. However, I attempted to enforce version ^8.2 and encountered the following error:

Problem 1
    - Root composer.json requires jolicode/automapper-bundle 8.2.2 (exact version match: 8.2.2 or 8.2.2.0), found jolicode/automapper-bundle[dev-feat/make-it-works-with-last-version, dev-release/v8.1.0, dev-fix/disallow-8.2, dev-main, 8.0.0, ..., 8.1.0] but it does not match the constraint.

I'm unsure how to configure the mapping in the YAML file with version 8.1.

Should I consider using the latest 9 beta instead?

[RFC] 9.0 Roadmap

Here is a list of things that would be nice to be available for a first version of the 9.0 :

  • Api Platform integration (PR in draft #95 ) no need to be 100% complete, it will be expiremental and fixes / support for specific features will come with other 9.X versions
  • Add a way to migrate from symfony serializer / api platform to automapper step by step (a.k.a specifying which object should use our normalizer, other will use existing normalizer) ref #98
  • Add a better DX to register mapper to build during cache warmup
  • Ensure some behavior of automapper is the same as the symfony / serializer by default when using the normalizer ref #101
  • Debug mode (command + profiler) inside symfony ref #103
  • Related to debug, think what would be best by default in dev env for loader (eval ? file ?) ref #102
  • Ensure cache related options are consistent and properly nammed

List of things that could be nice, but could be added after the first release :

  • Add a way to configure mapper other than using Attribute, like configuration file in PHP, xml or yaml
  • Add more configuration parameters for a specific mapper
    • Allow to disable / enable check attribute for a specific mapper and/or property
    • Allow to set default date format for a specific mapper and/or property
    • Allow to disable constructor for a specific mapper
    • Allow to map private properties for a specific mapper

Transform attributes in a specific order

Is there a way to transform attributes in a specific order and retrieve a transformed field from a previous transformer to another one ?

Consider we have this DTO

class ProfileDTO
{
    #[MapTo(name: 'source', transformer: SourceTransformer::class)]
    public int $sourceId;

    #[MapTo(name: 'city', transformer: CityTransformer::class)]
    public string $cityName;
}
$profileDTO = new ProfileDTO();
$profileDTO->sourceId = 1;
$profileDTO->cityName = 'Nice';
$automapper = AutoMapper::create(propertyTransformers: [$sourceTransformer, $cityTransformer]);
/** @var Profile $profile */
$profile = $automapper->map($profileDTO, Profile::class);
class SourceTransformer implements PropertyTransformerInterface
{
    public function __construct(private SourceRepository $sourceRepository) {}

    /**
     * @param int $value
     * @return Source
     */
    public function transform(mixed $value, array|object $source, array $context): mixed
    {
        return $this->sourceRepository->find($value);
    }

}
class SourceTransformer implements PropertyTransformerInterface
{
    public function __construct(private CityRepository $cityRepository) {}

    /**
     * @param CityDTO $value
     * @return City|null
    */
    public function transform(mixed $value, array|object $source, array $context): mixed
    {
        // Here I want to apply some logic regarding to the Source entity <------
    }

}

Debug mode

We could implement a debug mode for the automapper, here are some ideas:

  • simple: some verbose logs in a monolog channel
  • more sophisticated: a cli command which would print metadata and transformer used for a given class

Adding a transformer to an array of objects

Hello,

I'm trying to transform an array of objects (DTOs) into an array(entities), Is that something that the library is capable of handling?

Here's a snippet of the code:

/** @var ColourApi[] */
#[MapTo(transformer: ColourTransformer::class)]
public array $colours = [];

When looking at the automapper tab in the profiler although it attaches AutoMapper\Transformer\PropertyTransformer\PropertyTransformer there's no code inside.

I've the same setup for simple properties, so I know the problem is probably not the transformer.

Let me know if you need more context.

Thank you!

CustomTransformers not supported in Symfony

Hello guys,

I'm currently blocked from using this library properly in a Symfony application.

From documentation: https://jolicode.github.io/automapper/#/?id=map-manually-a-whole-object

In order to customize the mapping of a whole object, you can leverage AutoMapper\Transformer\CustomTransformer\CustomModelTransformerInterface.

Even though the code for custom transformers is in a main branch, it still doesn't work when doing normal installation as it was implemented after latest release 8.1.0.

And since my project directly depends on automapper-bundle I cannot just Composer override automapper package version to dev-main .

It would be awesome if we could have a new version with this feature out.

Thanks

PHP Parser version

Currently getting:

- jolicode/automapper[8.0.0, ..., 8.2.0] require nikic/php-parser ^4.0 -> found nikic/php-parser[v4.0.0, ..., v4.18.0] but the package is fixed to v5.0.2 (lock file version) by a partial update and that version does not match. Make sure you list it as an argument for the update command.

Using nikic/php-parser ^4.0 || ^5.0 in your dep tree would fix this.

Feature: Inheritance support

Synopsis:

abstract class Animal {}
class Dog extends Animal {}
class Cat extends Animal {}
class Fish extends Animal {}

abstract class AnimalDto {}
class DogDto extends AnimalDto {}
class CatDto extends AnimalDto {}
class FishDto extends AnimalDto {}

$dog = new Dog();
$cat = new Cat();
$fish = new Fish();

$dogDto = $autoMapper->map($dog, AnimalDto::class);   
$catDto = $autoMapper->map($cat, AnimalDto::class);   
$fishDto = $autoMapper->map($fish, AnimalDto::class);

$dog = $autoMapper->map($dogDto, Animal::class);
$cat = $autoMapper->map($catDto, Animal::class);
$fish = $autoMapper->map($fishDto, Animal::class);

My proposal for attaching metadata using attribute:

#[InheritanceMapping([
    Dog::class => DogDto::class,
    Cat::class => CatDto::class,
    Fish::class => FishDto::class,
])]
abstract class AnimalDto {}

Logic:

  • If the destination class has the InheritanceMapping attribute, then the mapper uses it directly
  • If the destination class is abstract, look for the InheritanceMapping attribute in the parents of the source class, starting from the immediate parent. If found, array_flip() the mapping and use it.

[Feature] introduce attribute `#[AutoMapper\Ignore]`

presently, the only way to ignore a property is to use serializer's #[Ignore] attribute (or to use automapper context's IGNORED_ATTRIBUTES, but this forces to rely on properties names, and cannot be used in as a generic mechanism)

Myabe we could then introduce its own ignore attribute for the automapper?

[CustomTransformer] Find & resolve variables TypeContext within an extracted `transform` method

Hello,
Let's take this example:

<?php

use App\AddressDTO;

final readonly class FooTransformer implements CustomModelTransformerInterface
{
    public function supports(array $sourceTypes, array $targetTypes): bool
    {
        return true;
    }

    public function transform(mixed $input): mixed
    {
        $addressDTO = new AddressDTO();
        $addressDTO->city = "{$input['city']} from custom model transformer";

        return $addressDTO;
    }
}

We can see that we are instanciating an AddressDTO, when extracting code, we don't extract that this statement needs the use App\AddressDTO;, we should do it to avoid issues when using CustomTransformers.

On how to do it, I think we need to make it in 3 steps:

  • Get all use statements in the file
  • Extract the transform method code
  • Check within the extracted code if we need anything that was in the use statements

DX initiative

Hey,

The objective is to make AutoMapper easier to work with for developpers.
In this issue I'll try to list all stuff developpers needs to fit their needs when using AutoMapper.

All the following stuff are examples and subject to change in their implementation.

  • Being able to give a custom property output for a given target
class Foo
{
  public function __construct(
    #[MapTo(('array', 'phrase')]
    #[MapTo(Bar::class, 'phrase')]
    public string $sentence,
  ) {
  }
}
  • Being able to give a custom property input for a given target
class Foo
{
  public function __construct(
    #[MapFrom(('array', 'phrase')]
    #[MapFrom(Bar::class, 'phrase')]
    public string $sentence,
  ) {
  }
}
  • Being able to map a field only if a certain condition is filled for a given target
class Foo
{
  public function __construct(
    #[MapIf(('array', fn() => true)]
    #[MapIf(Bar::class, fn() => false)]
    public string $sentence,
  ) {
  }
}
  • Having a way to override / custom the mapping of a property for a given transformation
final readonly class FromSourceCustomPropertyTransformer implements CustomPropertyTransformerInterface
{
    public function supports(string $source, string $target, string $propertyName): bool
    {
        return $source === UserDTO::class && $target === 'array' && $propertyName === 'name';
    }

    public function transform(mixed $input): mixed
    {
        return "{$input} set by custom property transformer";
    }
}
  • Having a way to override / custom the mapping for a given transformation as a whole
final readonly class FromTargetCustomModelTransformer implements CustomModelTransformerInterface
{
    public function supports(string $source, string $target): bool
    {
        return $source === 'array' && $target === AddressDTO::class;
    }

    public function transform(mixed $input): mixed
    {
        $addressDTO = new \AutoMapper\Tests\Fixtures\AddressDTO();
        $addressDTO->city = "{$input['city']} from custom model transformer";

        return $addressDTO;
    }
}
  • Having a way to add custom code during some steps of the transformation:
    • beforeInitialization
    • afterInitialization
    • afterHydratation
class Foo
{
  public function __construct(
    public string $sentence,
  ) {
  }

  #[MapEvent(Event::AFTER_INITIALIZATION)]
  public function doctrineHook(mixed $object): void
  {
    // link my entity to Doctrine UOW
  }
}

ObjectTransformer is not available when custom MapperConfigurationInterface is processed

Original issue: janephp/janephp#745
Reported by: @weaverryan

Jane version(s) affected: 7.5.3

Description
Hi!

In the AutoMapper bundle, if you create a custom MapperConfigurationInterface and call $metadata->getPropertiesMapping(), the mappings will be generated BEFORE the ObjectTransformer was loaded into the system. The problem comes with how the objects are instantiated:

A) When the container creates theJane\Component\AutoMapper\Transformer\ChainTransformerFactory service, in the compiled container, it eventually calls:

$instance->addTransformerFactory(new \Jane\Component\AutoMapper\Transformer\ObjectTransformerFactory(($container->services['Jane\\Component\\AutoMapper\\AutoMapperInterface'] ?? $container->load('getAutoMapperInterfaceService'))));

In order to create the ObjectTransformerFactory, it needs to load the AutoMapperInterface service so it can be passed to it.

B) But, when the AutoMapperInterface is being created, in the cached container, it will call:

$instance->addMapperConfiguration(new \App\Automapper\UserToUserApiMapperConfiguration());

(where UserToUserApiMapperConfiguration is the custom mapper configuration class).

C) The AutoMapper::addMapperConfiguration() method - https://github.com/janephp/janephp/blob/next/src/Bundle/AutoMapperBundle/AutoMapper.php#L13 - causes the process() method to be called on the custom mapper configuration. However, at this point, the ObjectTransformerFactory hasn't yet finished being added to the system.

This is a minor issue - it can just cause WTF moments. I couldn't figure out why a property was NOT apparently being mapped... but then WOULD be mapped in the end.

How to reproduce

symfony new reproducing_jane_missing_object_transformer
cd reproducing_jane_missing_object_transformer
composer require jane-php/automapper-bundle

Create a mapping config class - throw an exception:

<?php

namespace App;

use Jane\Bundle\AutoMapperBundle\Configuration\MapperConfigurationInterface;
use Jane\Component\AutoMapper\MapperGeneratorMetadataInterface;

class TestingMapperConfiguration implements MapperConfigurationInterface
{
    public function process(MapperGeneratorMetadataInterface $metadata): void
    {
        throw new \Exception();
    }

    public function getSource(): string
    {
        return 'foo';
    }

    public function getTarget(): string
    {
        return 'bar';
    }
}

Then, use the AutoMapper somewhere so that it's not removed from the container:

<?php

namespace App\Controller;

use Jane\Component\AutoMapper\AutoMapperInterface;

class AnyController
{
    public function __construct(AutoMapperInterface $autoMapper)
    {        
    }
}

Finally:

php bin/console cache:clear -vvv

If you look at the stacktrace, you'll see something like:

ContainerTWxKxjp\App_KernelDevDebugContainer->load() at /Users/weaverryan/Sites/os/reproducers/reproducing_jane_missing_object_transformer/var/cache/de_/ContainerTWxKxjp/getChainTransformerFactoryService.php:42

If you look at that line, you'll see it starting the instantiation of the AutoMapperInterface service before the ObjectTransformerFactory is complete, which proves that the custom mapper config class is called before the ObjectTransformerFactory is present.

Possible Solution
One possibility is to inject the AutoMapperInterface dependency into ObjectTransformerFactory lazily in some way so that the chain transformer factory can finish instantiating without triggering the instantiation of the auto mapper.

Cheers!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.