mirror of
https://github.com/filamentphp/filament.git
synced 2026-09-24 15:42:09 +08:00
[4.x] Bug: Nested JSON fields break with same-name helper/accessor method on model (#18199)
* snapshot updates * tests and test model/resource updates * `hasAttribute()` check As a workaround for Laravel's false-positives in `hasRelationship()`. * Update Post.php Signed-off-by: Dan Harrin <git@danharrin.com> * Update config method to use array syntax Signed-off-by: Dan Harrin <git@danharrin.com> * Update HasCellState.php Signed-off-by: Dan Harrin <git@danharrin.com> * clean up elsewhere --------- Signed-off-by: Dan Harrin <git@danharrin.com> Co-authored-by: Dan Harrin <git@danharrin.com>
This commit is contained in:
co-authored by
Dan Harrin
parent
7986448fac
commit
72ead269c9
@@ -298,7 +298,7 @@ class CheckboxList extends Field implements Contracts\CanDisableOptions, Contrac
|
||||
|
||||
$record = $this->getModelInstance();
|
||||
|
||||
if (! $record->isRelation($name)) {
|
||||
if ($record->hasAttribute($name) || (! $record->isRelation($name))) {
|
||||
throw new LogicException("The relationship [{$name}] does not exist on the model [{$this->getModel()}].");
|
||||
}
|
||||
|
||||
|
||||
@@ -568,6 +568,12 @@ class ModalTableSelect extends Field
|
||||
$relationshipName = $this->getRelationshipName();
|
||||
|
||||
foreach (explode('.', $relationshipName) as $nestedRelationshipName) {
|
||||
if ($record->hasAttribute($nestedRelationshipName)) {
|
||||
$relationship = null;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (! $record->isRelation($nestedRelationshipName)) {
|
||||
$relationship = null;
|
||||
|
||||
|
||||
@@ -203,7 +203,7 @@ class MorphToSelect extends Component
|
||||
|
||||
$relationshipName = $this->getName();
|
||||
|
||||
if (! $record->isRelation($relationshipName)) {
|
||||
if ($record->hasAttribute($relationshipName) || (! $record->isRelation($relationshipName))) {
|
||||
throw new LogicException("The relationship [{$relationshipName}] does not exist on the model [{$this->getModel()}].");
|
||||
}
|
||||
|
||||
|
||||
@@ -1119,7 +1119,7 @@ class Repeater extends Field implements CanConcealComponents, HasExtraItemAction
|
||||
|
||||
$relationshipName = $this->getRelationshipName();
|
||||
|
||||
if (! $record->isRelation($relationshipName)) {
|
||||
if ($record->hasAttribute($relationshipName) || (! $record->isRelation($relationshipName))) {
|
||||
throw new LogicException("The relationship [{$relationshipName}] does not exist on the model [{$this->getModel()}].");
|
||||
}
|
||||
|
||||
|
||||
@@ -1255,6 +1255,12 @@ class Select extends Field implements Contracts\CanDisableOptions, Contracts\Has
|
||||
$relationshipName = $this->getRelationshipName();
|
||||
|
||||
foreach (explode('.', $relationshipName) as $nestedRelationshipName) {
|
||||
if ($record->hasAttribute($nestedRelationshipName)) {
|
||||
$relationship = null;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (! $record->isRelation($nestedRelationshipName)) {
|
||||
$relationship = null;
|
||||
|
||||
|
||||
@@ -253,6 +253,12 @@ class TableSelect extends Field
|
||||
$relationshipName = $this->getRelationshipName();
|
||||
|
||||
foreach (explode('.', $relationshipName) as $nestedRelationshipName) {
|
||||
if ($record->hasAttribute($nestedRelationshipName)) {
|
||||
$relationship = null;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (! $record->isRelation($nestedRelationshipName)) {
|
||||
$relationship = null;
|
||||
|
||||
|
||||
@@ -71,7 +71,7 @@ trait BelongsToTenant
|
||||
{
|
||||
$relationshipName = static::getTenantOwnershipRelationshipName();
|
||||
|
||||
if (! $record->isRelation($relationshipName)) {
|
||||
if ($record->hasAttribute($relationshipName) || (! $record->isRelation($relationshipName))) {
|
||||
$resourceClass = static::class;
|
||||
$recordClass = $record::class;
|
||||
|
||||
@@ -93,7 +93,7 @@ trait BelongsToTenant
|
||||
{
|
||||
$relationshipName = static::getTenantRelationshipName();
|
||||
|
||||
if (! $tenant->isRelation($relationshipName)) {
|
||||
if ($tenant->hasAttribute($relationshipName) || (! $tenant->isRelation($relationshipName))) {
|
||||
$resourceClass = static::class;
|
||||
$tenantClass = $tenant::class;
|
||||
|
||||
|
||||
@@ -30,6 +30,12 @@ trait CanGetStateFromRelationships
|
||||
$relationship = null;
|
||||
|
||||
foreach (explode('.', $statePath ?? $this->getStateRelationshipName()) as $nestedRelationshipName) {
|
||||
if ($record->hasAttribute($nestedRelationshipName)) {
|
||||
$relationship = null;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (! $record->isRelation($nestedRelationshipName)) {
|
||||
$relationship = null;
|
||||
|
||||
|
||||
@@ -188,6 +188,10 @@ trait HasCellState
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($record->hasAttribute((string) str($name)->before('.'))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $record->isRelation((string) str($name)->before('.'));
|
||||
}
|
||||
|
||||
@@ -221,6 +225,10 @@ trait HasCellState
|
||||
$relationship = null;
|
||||
|
||||
foreach ($nameParts as $namePart) {
|
||||
if ($record->hasAttribute($namePart)) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (! $record->isRelation($namePart)) {
|
||||
break;
|
||||
}
|
||||
@@ -329,6 +337,10 @@ trait HasCellState
|
||||
$lastPart = array_pop($nameParts);
|
||||
|
||||
foreach ($nameParts as $namePart) {
|
||||
if ($record->hasAttribute($namePart)) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (! $record->isRelation($namePart)) {
|
||||
break;
|
||||
}
|
||||
@@ -352,6 +364,10 @@ trait HasCellState
|
||||
$lastPart = array_pop($nameParts);
|
||||
|
||||
foreach ($nameParts as $namePart) {
|
||||
if ($record->hasAttribute($namePart)) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (! $record->isRelation($namePart)) {
|
||||
break;
|
||||
}
|
||||
@@ -375,6 +391,10 @@ trait HasCellState
|
||||
$inverseRelationshipParts = [];
|
||||
|
||||
foreach ($nameParts as $namePart) {
|
||||
if ($record->hasAttribute($namePart)) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (! $record->isRelation($namePart)) {
|
||||
break;
|
||||
}
|
||||
@@ -391,11 +411,11 @@ trait HasCellState
|
||||
)
|
||||
->camel();
|
||||
|
||||
if (! $record->isRelation($inverseNestedRelationshipName)) {
|
||||
if ($record->hasAttribute($inverseNestedRelationshipName) || (! $record->isRelation($inverseNestedRelationshipName))) {
|
||||
// The conventional relationship doesn't exist, but we can
|
||||
// attempt to use the original relationship name instead.
|
||||
|
||||
if (! $record->isRelation($namePart)) {
|
||||
if ($record->hasAttribute($namePart) || (! $record->isRelation($namePart))) {
|
||||
$recordClass = $record::class;
|
||||
|
||||
throw new LogicException("When trying to guess the inverse relationship for column [{$this->getName()}], relationship [{$inverseNestedRelationshipName}] was not found on model [{$recordClass}]. Please define a custom [inverseRelationship()] for this column.");
|
||||
@@ -424,6 +444,10 @@ trait HasCellState
|
||||
$relationshipParts = [];
|
||||
|
||||
foreach ($nameParts as $namePart) {
|
||||
if ($record->hasAttribute($namePart)) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (! $record->isRelation($namePart)) {
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -786,6 +786,12 @@ class SelectColumn extends Column implements Editable, HasEmbeddedView
|
||||
$relationshipName = $this->getOptionsRelationshipName();
|
||||
|
||||
foreach (explode('.', $relationshipName) as $nestedRelationshipName) {
|
||||
if ($record->hasAttribute($nestedRelationshipName)) {
|
||||
$relationship = null;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (! $record->isRelation($nestedRelationshipName)) {
|
||||
$relationship = null;
|
||||
|
||||
|
||||
@@ -64,6 +64,12 @@ trait HasRelationship
|
||||
$relationshipName = $this->getRelationshipName();
|
||||
|
||||
foreach (explode('.', $relationshipName) as $nestedRelationshipName) {
|
||||
if ($record->hasAttribute($nestedRelationshipName)) {
|
||||
$relationship = null;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (! $record->isRelation($nestedRelationshipName)) {
|
||||
$relationship = null;
|
||||
|
||||
|
||||
+6
@@ -232,6 +232,12 @@ class IsRelatedToOperator extends Operator
|
||||
$relationship = null;
|
||||
|
||||
foreach (explode('.', $constraint->getRelationshipName()) as $nestedRelationshipName) {
|
||||
if ($record->hasAttribute($nestedRelationshipName)) {
|
||||
$relationship = null;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (! $record->isRelation($nestedRelationshipName)) {
|
||||
$relationship = null;
|
||||
|
||||
|
||||
@@ -393,6 +393,12 @@ class Group extends Component
|
||||
$relationship = null;
|
||||
|
||||
foreach (explode('.', $name ?? $this->getRelationshipName()) as $nestedRelationshipName) {
|
||||
if ($record->hasAttribute($nestedRelationshipName)) {
|
||||
$relationship = null;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (! $record->isRelation($nestedRelationshipName)) {
|
||||
$relationship = null;
|
||||
|
||||
|
||||
@@ -248,6 +248,12 @@ trait CanSearchRecords
|
||||
$relationship = null;
|
||||
|
||||
foreach (str($name)->beforeLast('.')->explode('.')->all() as $nestedRelationshipName) {
|
||||
if ($record->hasAttribute($nestedRelationshipName)) {
|
||||
$relationship = null;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (! $record->isRelation($nestedRelationshipName)) {
|
||||
$relationship = null;
|
||||
|
||||
|
||||
+2
@@ -31,6 +31,8 @@ class PostForm
|
||||
->columnSpanFull(),
|
||||
TextInput::make('title')
|
||||
->required(),
|
||||
Textarea::make('config')
|
||||
->columnSpanFull(),
|
||||
Textarea::make('json')
|
||||
->columnSpanFull(),
|
||||
Textarea::make('json_array_of_objects')
|
||||
|
||||
+2
@@ -47,6 +47,8 @@ class CreatePostWithFields extends Component implements HasActions, HasSchemas
|
||||
->columnSpanFull(),
|
||||
TextInput::make('title')
|
||||
->required(),
|
||||
Textarea::make('config')
|
||||
->columnSpanFull(),
|
||||
Textarea::make('json')
|
||||
->columnSpanFull(),
|
||||
Textarea::make('json_array_of_objects')
|
||||
|
||||
+2
@@ -39,6 +39,8 @@ class PostResource extends Resource
|
||||
->columnSpanFull(),
|
||||
Forms\Components\TextInput::make('title')
|
||||
->required(),
|
||||
Forms\Components\Textarea::make('config')
|
||||
->columnSpanFull(),
|
||||
Forms\Components\Textarea::make('json')
|
||||
->columnSpanFull(),
|
||||
Forms\Components\Textarea::make('json_array_of_objects')
|
||||
|
||||
+5
@@ -51,6 +51,8 @@ class PostResource extends Resource
|
||||
->columnSpanFull(),
|
||||
TextInput::make('title')
|
||||
->required(),
|
||||
Textarea::make('config')
|
||||
->columnSpanFull(),
|
||||
Textarea::make('json')
|
||||
->columnSpanFull(),
|
||||
Textarea::make('json_array_of_objects')
|
||||
@@ -77,6 +79,9 @@ class PostResource extends Resource
|
||||
->placeholder('-')
|
||||
->columnSpanFull(),
|
||||
TextEntry::make('title'),
|
||||
TextEntry::make('config')
|
||||
->placeholder('-')
|
||||
->columnSpanFull(),
|
||||
TextEntry::make('json')
|
||||
->placeholder('-')
|
||||
->columnSpanFull(),
|
||||
|
||||
+2
@@ -30,6 +30,8 @@ class PostForm
|
||||
->columnSpanFull(),
|
||||
TextInput::make('title')
|
||||
->required(),
|
||||
Textarea::make('config')
|
||||
->columnSpanFull(),
|
||||
Textarea::make('json')
|
||||
->columnSpanFull(),
|
||||
Textarea::make('json_array_of_objects')
|
||||
|
||||
+3
@@ -26,6 +26,9 @@ class PostInfolist
|
||||
->placeholder('-')
|
||||
->columnSpanFull(),
|
||||
TextEntry::make('title'),
|
||||
TextEntry::make('config')
|
||||
->placeholder('-')
|
||||
->columnSpanFull(),
|
||||
TextEntry::make('json')
|
||||
->placeholder('-')
|
||||
->columnSpanFull(),
|
||||
|
||||
@@ -16,6 +16,7 @@ return new class extends Migration
|
||||
$table->unsignedTinyInteger('rating')->default(0);
|
||||
$table->json('tags')->nullable();
|
||||
$table->string('title');
|
||||
$table->json('config')->nullable();
|
||||
$table->json('json')->nullable();
|
||||
$table->json('json_array_of_objects')->nullable();
|
||||
$table->string('string_backed_enum')->nullable();
|
||||
|
||||
@@ -97,6 +97,7 @@ class PostsTable extends Component implements HasActions, HasSchemas, Tables\Con
|
||||
->searchable()
|
||||
->sortable(),
|
||||
Tables\Columns\TextColumn::make('json_array_of_objects.*.value'),
|
||||
Tables\Columns\TextColumn::make('config.setting'),
|
||||
Tables\Columns\TextColumn::make('author.json.foo')
|
||||
->searchable()
|
||||
->sortable(),
|
||||
|
||||
@@ -25,6 +25,7 @@ class Post extends Model
|
||||
'tags' => 'array',
|
||||
'json' => 'array',
|
||||
'json_array_of_objects' => 'array',
|
||||
'config' => 'array',
|
||||
];
|
||||
}
|
||||
|
||||
@@ -35,6 +36,11 @@ class Post extends Model
|
||||
return $this->belongsTo(User::class, 'author_id');
|
||||
}
|
||||
|
||||
public function config(string $key): mixed
|
||||
{
|
||||
return $this->config[$key] ?? null;
|
||||
}
|
||||
|
||||
protected static function newFactory()
|
||||
{
|
||||
return PostFactory::new();
|
||||
|
||||
@@ -320,6 +320,15 @@ it('can output values in a JSON array column of objects', function (): void {
|
||||
->assertTableColumnStateSet('json_array_of_objects.*.value', ['foo', 'bar', 'baz'], $post);
|
||||
});
|
||||
|
||||
it('can output values in a JSON column with a non-relationship accessor method', function (): void {
|
||||
$post = Post::factory()->create([
|
||||
'config' => ['setting' => 'foo'],
|
||||
]);
|
||||
|
||||
livewire(PostsTable::class)
|
||||
->assertTableColumnStateSet('config.setting', 'foo', $post);
|
||||
});
|
||||
|
||||
it('can state whether a column has extra attributes', function (): void {
|
||||
$post = Post::factory()->create();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user