Jump to content

How to override trait methods from another trait?


Go to solution Solved by gizmola,

Recommended Posts

The previous code that is working shown below along with a pre-persist event listener is used to create an auto-incrementing identifier (called publicId) for each type of entity on a per account/tenant basis.

I later had a need to get this publicId before the entity was persisted.  This made me consider that I am way overcomplicating matters and should just set this publicId when setting the entities tenant property, and I added the following method:

trait HasPublicIdTrait
{
    // other methods

    // Override parent to set entities publicid.
    public function setTenant(?Tenant $tenant): self
    {
        $this->tenant = $tenant;
        $tenant->generatePublicId($this);

        return $this;
    }
}

 

When trying it out, however, I got the following error:

Compile Error: Trait method App\Entity\MultiTenenacy\HasPublicIdTrait::setTenant has not been applied as App\Entity\Organization\Vendor::setTenant, because of collision with App\Entity\MultiTenenacy\BelongsToTenantTrait::set


Is it possible to make trait methods override other trait methods?   Any other suggestions other than just not having AbstractPublicIdTenantEntity extend AbstractPublicIdTenantEntity?

PREVIOUS CODE THAT IS WORKING

 

<?php
declare(strict_types=1);

namespace App\Entity\Organization;

use Doctrine\ORM\Mapping as ORM;
use App\Repository\Organization\TenantRepository;
use App\Entity\MultiTenenacy\HasPublicIdInterface;

#[ORM\Entity(repositoryClass: TenantRepository::class)]
class Tenant extends AbstractOrganization
{
    #[ORM\Column(type: 'json')]
    private array $publicIdStack = [];

    public function publicIdSetter(HasPublicIdInterface $hasPublicId): self
    {
        $publicIdIndex = $hasPublicId->getPublicIdIndex();
        $this->publicIdStack[$publicIdIndex] = ($this->publicIdStack[$publicIdIndex]??0) + 1;
        $hasPublicId->setPublicId($this->publicIdStack[$publicIdIndex]);

        return $this;
    }
}


 

<?php

namespace App\Entity\MyEntity;

use App\Entity\MultiTenenacy\AbstractPublicIdTenantEntity;

class MyEntity extends AbstractPublicIdTenantEntity
{
    public function getPublicIdIndex(): ?string
    {
        return 'my-entity';
    }
}

 

<?php
declare(strict_types=1);
namespace App\Entity\MultiTenenacy;

use App\Entity\Trait\NonIdentifyingIdTrait;

abstract class AbstractPublicIdTenantEntity extends AbstractTenantEntity implements HasPublicIdInterface
{
    use NonIdentifyingIdTrait;
    use HasPublicIdTrait;
}

 

<?php
declare(strict_types=1);
namespace App\Entity\MultiTenenacy;

use App\Entity\AbstractEntity;

abstract class AbstractTenantEntity extends AbstractEntity implements BelongsToTenantInterface
{
    use BelongsToTenantTrait;
}

 

 

<?php
declare(strict_types=1);
namespace App\Entity\MultiTenenacy;

use Doctrine\ORM\Mapping as ORM;
use App\Entity\Organization\Tenant;
use Symfony\Component\Serializer\Annotation\Ignore;

trait BelongsToTenantTrait
{
    #[ORM\ManyToOne(targetEntity: Tenant::class)]
    #[ORM\JoinColumn(nullable: false, onDelete: 'CASCADE')]
    #[Ignore]
    protected ?Tenant $tenant = null;

    public function getTenant(): Tenant
    {
        return $this->tenant;
    }

    public function setTenant(?Tenant $tenant): self
    {
        $this->tenant = $tenant;

        return $this;
    }
}

 

<?php
declare(strict_types=1);
namespace App\Entity\MultiTenenacy;

use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Serializer\Annotation\Groups;
use Symfony\Component\Serializer\Annotation\SerializedName;
use Symfony\Component\Serializer\Annotation\Ignore;
use App\Entity\Organization\Tenant;

#[ORM\UniqueConstraint(columns: ['public_id', 'tenant_id'])]
trait HasPublicIdTrait
{
    #[SerializedName('id')]
    #[Groups(['public_id:read'])]
    #[ORM\Column(type: 'integer')]
    protected ?int $publicId = null;

    public function getPublicId(): ?int
    {
        return $this->publicId;
    }

    public function setPublicId(int $publicId): HasPublicIdInterface
    {
        $this->publicId = $publicId;

        return $this;
    }

    public function generatePublicId(): HasPublicIdInterface
    {
        $this->getTenant()->publicIdSetter($this);

        return $this;
    }

    abstract public function getPublicIdIndex(): ?string;
}

 

  • Solution

In the PHP manual section Traits section.

You have 2 options:

  • Alias the conflicting method so it doesn't conflict
  • Specify an "insteadof" method statement in the use section of the class
  • Like 1

Thanks gizmola,  Looks like insteadof is exactly what I want and think/hope the following will do the trick.  Not positive, but wouldn't using an alias break an interface?

abstract class AbstractPublicIdTenantEntity extends AbstractTenantEntity implements HasPublicIdInterface
{
    use NonIdentifyingIdTrait, HasPublicIdTrait {
        HasPublicIdTrait::setTenant insteadof NonIdentifyingIdTrait;
    }
	
	// bla bla bla
}

 

Actually, my earlier code was incorrect.  NonIdentifyingIdTrait doesn't have method setTenant() but BelongsToTenantTrait does. When I changed it to the following, I received error: "Compile Error: Required Trait App\Entity\MultiTenenacy\BelongsToTenantTrait wasn't added to App\Entity\MultiTenenacy\AbstractPublicIdTenantEntity

abstract class AbstractPublicIdTenantEntity extends AbstractTenantEntity implements HasPublicIdInterface
{
    use NonIdentifyingIdTrait, HasPublicIdTrait {
        HasPublicIdTrait::setTenant insteadof BelongsToTenantTrait;
    }
}

I have since changed it to the following and no longer get the error.  Is there any concern about having both AbstractTenantEntity and AbstractPublicIdTenantEntity which extends AbstractTenantEntity use the same trait?

abstract class AbstractPublicIdTenantEntity extends AbstractTenantEntity implements HasPublicIdInterface
{    
    use NonIdentifyingIdTrait, HasPublicIdTrait, BelongsToTenantTrait {
        HasPublicIdTrait::setTenant insteadof BelongsToTenantTrait;
    }
}


Also, I am rethinking whether aliases might be applicable but am still uncertain of the implications of the following from the docs.  It does not rename the method?  What does this mean?

Quote

Since this only allows one to exclude methods, the as operator can be used to add an alias to one of the methods. Note the as operator does not rename the method and it does not affect any other method either.

 

An Alias only affects the class it is defined in.  If I alias a method with "MyTrait::foo as bar" then within that class you would need to use self::bar.  

Assuming this is in the definition of class A, then the original A::foo would be called. 

It's worth also noting that if the methods are public, trying to use a trait that has an overlap simply results in the non-inclusion of the trait.

<?php

trait Basic {
    public function whichFoo() {
        return 'basicFoo';
    }
}

class Foo {
    use Basic;
    public function whichFoo() {
        return 'FooFoo';
    }
}

$f = new Foo();

echo $f->whichFoo();

# outputs FooFoo

 

I can see that you are trying to use these to handle relationships between doctrine entities, although I don't really know what these specific id methods are for, but I have to question your approach vs. a more traditional setup, given all the troubles you are having.

 

 

This thread is more than a year old. Please don't revive it unless you have something important to add.

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...

Important Information

We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue.