Skip to content

Circular references with promoted public properties cause infinite loops #271

@SanderVerkuil

Description

@SanderVerkuil

Assuming we have the following code:

class Category
{
    /** @var Post[] */
    public array $posts = [];

    public function __construct(
        public string $name
    ) {
    }
}

class Post
{
    public function __construct(
        public string $name,
        public Category $category
    ) {
    }
}

class CategoryDTO
{
    /**
     * @var PostDTO[]
     */
    public array $posts = [];

    public function __construct(
        public string $name,
    ) {
    }
}
class PostDTO
{
    public function __construct(
        public string $name,
        public CategoryDTO $category,
    )  {
    }
}

the generated mapper becomes:

/** @param \AutoMapper\Tests\Fixtures\Category $value */
    public function &map($value, array $context = []): mixed
    {
        if (null === $value) {
            return $value;
        }
        $sourcehash = spl_object_hash($value) . 'AutoMapper\Tests\Fixtures\CategoryDTO';
        if (\AutoMapper\MapperContext::shouldHandleCircularReference($context, $sourcehash)) {
            return \AutoMapper\MapperContext::handleCircularReference($context, $sourcehash, $value);
        }
        /** @var \AutoMapper\Tests\Fixtures\CategoryDTO|null $result */
        $result = $context['target_to_populate'] ?? null;
        if (null === $result) {
            $constructargs = [];
            if (isset($value->name)) {
                $constructargs['name'] = $value->name;
            } elseif (\AutoMapper\MapperContext::hasConstructorArgument($context, 'AutoMapper\Tests\Fixtures\CategoryDTO', 'name')) {
                $constructargs['name'] = \AutoMapper\MapperContext::getConstructorArgument($context, 'AutoMapper\Tests\Fixtures\CategoryDTO', 'name');
            } else {
                $constructargs['name'] = throw new \AutoMapper\Exception\MissingConstructorArgumentsException('Cannot create an instance of "AutoMapper\Tests\Fixtures\CategoryDTO" from mapping data because its constructor requires the following parameters to be present : "$name".', 0, null, ['name'], 'AutoMapper\Tests\Fixtures\CategoryDTO');
            }
            $result = new \AutoMapper\Tests\Fixtures\CategoryDTO(...$constructargs);
        }
        else {
            $context = \AutoMapper\MapperContext::withReference($context, $sourcehash, $result);
            $context = \AutoMapper\MapperContext::withIncrementedDepth($context);
            if (\AutoMapper\MapperContext::isAllowedAttribute($context, 'name', function () use ($value) {
                return !isset($value->name) and null === $value->name;
            }, !array_key_exists('name', (array) $value)) && (!array_key_exists('groups', $context) || !$context['groups'])) {
                $result->name = $value->name;
            }
        }
        if (\AutoMapper\MapperContext::isAllowedAttribute($context, 'posts', function () use ($value) {
            return !isset($value->posts) and null === $value->posts;
        }, !array_key_exists('posts', (array) $value)) && (!array_key_exists('groups', $context) || !$context['groups'])) {
            $values = $context['deep_target_to_populate'] ?? false ? isset($result->posts) ? $result->posts : [] : [];
            foreach ($value->posts ?? [] as $key => $value_1) {
                $values[] =& $this->mappers['Mapper_AutoMapper\Tests\Fixtures\Post_AutoMapper\Tests\Fixtures\PostDTO']->map($value_1, \AutoMapper\MapperContext::withNewContext($context, 'posts'));
            }
            $result->posts = $values;
        }
        return $result;
    }

As we can see, the context is only updated when we specify a target to fill. Otherwise it will cause an infinite loop. I would expect the mapper to handle this case as well.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions