Skip to content

Model attribute resolution is silently order-dependent when multiple traits declare the same attribute #60953

Closed

Description

@Carnicero90

Laravel Version

13.22.0

PHP Version

8.5.6

Database Driver & Version

No response

Description

Since #60566 (and #60519 for queue attributes), class-level attributes such as #[Table], #[Connection], etc. are also resolved from traits used by the model.
When two or more traits on the same class declare the same attribute and the class itself does not, the conflict is resolved silently: the attribute from the first trait in the use statement wins, because resolveClassAttribute() iterates ReflectionClass::getTraits() and returns on the first match.
This means that the class behaviour can be altered by a non-semantic change (like a different linting rule, a renaming, etc.).
I'm not sure what the correct solution would be, other than avoiding the short-circuiting on the first match, since every order-based heuristic seems shaky to me.
/cc @jackbayliss as the author of both PRs, in case you have thoughts on this

Steps To Reproduce

<?php

#[\Illuminate\Database\Eloquent\Attributes\Table(name: 'from_alpha')]
trait CarriesAlphaTable
{
}

#[\Illuminate\Database\Eloquent\Attributes\Table(name: 'from_beta')]
trait CarriesBetaTable
{
}

class OrderAlphaFirst extends \Illuminate\Database\Eloquent\Model
{
    use CarriesAlphaTable, CarriesBetaTable;
}

class OrderBetaFirst extends \Illuminate\Database\Eloquent\Model
{
    use CarriesBetaTable, CarriesAlphaTable;
}

dump((new OrderAlphaFirst)->getTable()); // "from_alpha"
dump((new OrderBetaFirst)->getTable());  // "from_beta"

Activity

  1. added a commit that references this issue on Jul 31, 2026
    841a4e4
  2. jackbayliss commented on Jul 31, 2026

    @jackbayliss
    Contributor

    @Carnicero90 Thanks for the ping, I guess it should probably throw some kind of exception in this case. Similar to https://3v4l.org/nBWcS

    But, I expect most people that use attrs on traits would know not to do this kind of thing. Should be addressed I imagine though..

  3. Carnicero90 commented on Jul 31, 2026

    @Carnicero90
    ContributorAuthor

    I guess, though they could also come from different vendors, in which case neither trait author can know about the conflict. I was thinking about an exception as well, although it implies always iterating over all traits instead of short-circuiting on the first match (and sooner or later recursively for traits included via trait composition as well). Not a serious performance concern, but still.
    Btw this issue was mainly meant to open a discussion on how to handle this, more than a push for an immediate fix

  4. locked and limited conversation to collaborators on Aug 4, 2026
  5. converted this issue into a discussion #61038 on Aug 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions