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

Make better discriminator #196

Open
Norbytus opened this issue Oct 9, 2024 · 0 comments
Open

Make better discriminator #196

Norbytus opened this issue Oct 9, 2024 · 0 comments

Comments

@Norbytus
Copy link

Norbytus commented Oct 9, 2024

I try us mapper with symfony discriminator, but it's not what i expected,
For example

// I have Source Interface or Abstract class
interface SourceInterface
{
}

/// And his impl
final readonly class SourceImpl implements SourceInterface
{
}

/// And i have target class for mapping, its Interface or Abstract too

interface TargetInterface
{
}

final readonly class TargetImpl implements TargetInterface
{
}

In usage something like

$source = new Source();

// For interface
$result = $this->mapper->map($source, TargetInterface::class);

// And for abstract

$result = $this->mapper->map($source, AbstractTarget::class, (new MapperContext())->setConstructorArgument(AbstractTarget::class, 'property', 'value')->toArray();

I try imp with event listener and replace target

#[\Attribute(\Attribute::TARGET_CLASS)]
final readonly class Discriminator
{
    /**
     * @param class-string $baseClass
     * @param array<class-string, class-string> $map 
     */
    public function __construct(
        public string $baseClass,
        public array $map,
    ) { }
}

#[AsEventListener(GenerateMapperEvent::class)]
final readonly class DiscriminatorListener
{
    public function __invoke(GenerateMapperEvent $event): void
    {
        if (!$event->mapperMetadata->targetReflectionClass) {
            return;
        }

        $attributes = $event->mapperMetadata
            ->targetReflectionClass
            ->getAttributes(Discriminator::class);

        if (count($attributes) === 0) {
            return;
        }

        foreach ($attributes as $attr) {
            $discriminatorAttribute = $attr->newInstance();

            $baseClassRef = new ReflectionClass($discriminatorAttribute->baseClass);

            if (!$event->mapperMetadata->sourceReflectionClass->isSubclassOf($baseClassRef)) {
                continue;
            }

            if (!$baseClassRef->isInterface() && !$baseClassRef->isAbstract()) {
                throw new BadMapDefinitionException(sprintf(
                    'Required `baseClass` should be abstract or interface in "%s" attribute on "%s" class.',
                    Discriminator::class,
                    $event->mapperMetadata->targetReflectionClass->getName(),
                ));
            }

            foreach ($discriminatorAttribute->map as $source => $target) {
                $ref = new ReflectionClass($source);

                if (!$ref->isSubclassOf($baseClassRef->getName())) {
                    throw new BadMapDefinitionException(sprintf(
                        'Required value of `map` should be subclass of %s in "%s" attribute on "%s" class.',
                        $baseClassRef->getName(),
                        Discriminator::class,
                        $event->mapperMetadata->targetReflectionClass->getName(),
                    ));
                }

                if ($ref->getName() === $event->mapperMetadata->sourceReflectionClass->getName()) {
                    $event->mapperMetadata->target = $target;
                }
            }
        }
    }
}

And it's work, but i still can't get default construct argument from context, because it's by from context by class name

RFC for example

interface SourceInterface
{
}

final readonly class SourceImpl implements SourceInterface
{
}

#[Discriminator(
    SourceInterface::class, //Base class for source
    [
        SourceImpl::class => TargetImpl::class, // In mapping get check source class and his mapping
    ]
)]
interface TargetInterface
{
}

final readonly class TargetImpl implements TargetInterface
{
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant