Skip to content

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

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"

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