filament-adjacency-list icon indicating copy to clipboard operation
filament-adjacency-list copied to clipboard

[Bug]: Since filament update relationship method does not exists any more

Open pepijndik opened this issue 8 months ago • 7 comments
trafficstars

What happened?

Did composer update and upgrade, to update some dependencies. Sadly, i didn't test it and then go a head with other stuff and cant find which versions i was using first.

i think Filament removed the ->relationship method to all Field components except Select and checkbox.

Method Saade\FilamentAdjacencyList\Forms\Components\AdjacencyList::relationship does not exist.

How to reproduce the bug

Composer update / upgrade to latest laravel 11.44.1 and Filament 3.3.4

Package Version

3.2.2

PHP Version

8.2

Laravel Version

11.44.1

Which operating systems does with happen with?

No response

Notes

No response

pepijndik avatar Mar 12 '25 12:03 pepijndik

I also noticed this issue with the orderColumn method. Do you know since which Filament version it's updated? @pepijndik

Baspa avatar Mar 18 '25 18:03 Baspa

I also noticed this issue with the orderColumn method. Do you know since which Filament version it's updated? @pepijndik

Sadly i cant find it exactly since all my resend commits have minor updates in it and never checkt it...

pepijndik avatar Mar 18 '25 18:03 pepijndik

I have the same problem. I downgraded filament to version 3.3.0 with AdjacencyList 3.2.2 and it still didn’t work. I brought AdjacencyList 3.2.1 and everything started working again

slamservice avatar Mar 19 '25 10:03 slamservice

Upgraded: "filament/filament": "3.3.2", "saade/filament-adjacency-list": "3.2.1",

All OK !

slamservice avatar Mar 19 '25 11:03 slamservice

Thanks @slamservice, that seems to work!

Baspa avatar Mar 20 '25 08:03 Baspa

I'm using

AdjacencyList::make('topics')->relationship('topics')

With: filament/filament v3.3.10 saade/filament-adjacency-list v3.2.2

And the error still occurs, any help?

dfb-systems avatar Apr 08 '25 04:04 dfb-systems

Try to downgrade the filamant-adjacency-list to v3.2.1 as mentioned above.. @dfb-systems

Baspa avatar Apr 08 '25 07:04 Baspa

Any updates on issue?

xGrz avatar May 12 '25 15:05 xGrz

If you need any help on this issue please let me know, we need this in some of our packages @saade

Baspa avatar May 20 '25 17:05 Baspa

I noticed that the trait Saade\FilamentAdjacencyList\Forms\Components\Concerns\HasRelationship has been removed in the latest version.

That cause this issue.

fadlee avatar May 21 '25 21:05 fadlee

If you still have this problem try adding this to composer.json @fadlee.

"repositories": {
    "saade/filament-adjacency-list": {
        "type": "git",
        "url": "[email protected]:backstagephp/filament-adjacency-list.git"
    }
}

Casmo avatar Jun 03 '25 11:06 Casmo

if you still have problems:

"require": {
"saade/filament-adjacency-list": "3.x-dev as 3.2.2",
      "repositories": [
       {
           "type": "vcs",
           "url": "https://github.com/backstagephp/filament-adjacency-list.git"
       }
   ],

slamservice avatar Jun 20 '25 10:06 slamservice

If you still have this problem try adding this to composer.json @fadlee.

"repositories": { "saade/filament-adjacency-list": { "type": "git", "url": "[email protected]:backstagephp/filament-adjacency-list.git" } }

doesn't work for me:

Cloning failed using an ssh key for authentication, enter your GitHub credentials to access private repos
When working with _public_ GitHub repositories only, head here to retrieve a token:
https://github.com/settings/tokens/new?scopes=&description=Composer+on+DK-CPH-K2D6H69KMX+2025-08-26+1245
This token will have read-only permission for public information only.
When you need to access _private_ GitHub repositories as well, go to:
https://github.com/settings/tokens/new?scopes=repo&description=Composer+on+DK-CPH-K2D6H69KMX+2025-08-26+1245
Note that such tokens have broad read/write permissions on your behalf, even if not needed by Composer.
Tokens will be stored in plain text in "/Users/jhededam/.composer/auth.json" for future use by Composer.
For additional information, check https://getcomposer.org/doc/articles/authentication-for-private-packages.md#github-oauth

synsyst avatar Aug 26 '25 12:08 synsyst

Did you make a token? https://github.com/settings/tokens/new?scopes=&description=Composer+on+DK-CPH-K2D6H69KMX+2025-08-26+1245

Casmo avatar Aug 26 '25 13:08 Casmo

if you still have problems:

"require": {
"saade/filament-adjacency-list": "3.x-dev as 3.2.2",
      "repositories": [
       {
           "type": "vcs",
           "url": "https://github.com/backstagephp/filament-adjacency-list.git"
       }
   ],

tried this -

after a bit, geting this error - dunno if it's evident what's wrong, happens when I try to save the record after I've added items in the adjacency list:

Illuminate\Database\Eloquent\Relations\HasOneOrMany::save(): Argument #1 ($model) must be of type Illuminate\Database\Eloquent\Model, null given

form:

AdjacencyList::make('projectTasks')
                                                ->relationship('projectTasks')          // Define the relationship
                                                ->labelKey('name')                  // Customize the label key to your model's column
                                                ->childrenKey('childTasks')           // Customize the children key to the relationship's method name
                                                ->form([                            // Define the form
                                                    TextInput::make('name')
                                                        ->label(__('Name'))
                                                        ->required(),
                                                ])
                                                ->addAction(fn (Action $action): Action => $action
                                                        ->label(__('Add Task'))
                                                        ->icon('heroicon-o-plus')
                                                        ->color('primary')
                                                    )
                                                    ->addChildAction(fn (Action $action): Action => $action
                                                        ->label(__('Add Subtask'))
                                                        ->icon('heroicon-o-plus-circle')
                                                        ->color('success')
                                                    )

relationships:

  public function projectTasks(): HasMany
   {
       return $this->hasMany(ProjectTask::class)->whereNull('parent_task_id')->with('childTasks')->orderBy('name');
   }
  public function childTasks(): HasMany
  {
    return $this->hasMany(ProjectTask::class, 'parent_task_id')->with('childTasks')->orderBy('name');
  }
use Staudenmeir\LaravelAdjacencyList\Eloquent\HasRecursiveRelationships;

class Project extends Model
{
    use HasFactory, HasUuids, HasComments, SoftDeletes, LogsActivity, Versionable, Userstamps, HasRecursiveRelationships;

se Staudenmeir\LaravelAdjacencyList\Eloquent\HasRecursiveRelationships;

class ProjectTask extends Model
{
  use HasFactory, HasUuids, SoftDeletes, LogsActivity, Versionable, Userstamps, HasRecursiveRelationships;

@Casmo perhaps you have insights here as well? Seems the error comes since there's no model instance to write to as I understand it. Scope is we have project records, and under there we want to be able to create tasks and sub-tasks and mange it from within the project record. The table for project tasks is its own table and has its own model.

Image adding a record in db manually and refreshing page correctly shows the entry - so I guess relationships kinda work?

synsyst avatar Aug 26 '25 13:08 synsyst

@Casmo nope I didn't - missed that part. Currently just fighting this package and trying to get it to work - quite the battle, but it seems to be the only thing out there that kinda fills my need.

Install worked with the VCS instead.

synsyst avatar Aug 26 '25 13:08 synsyst

@Casmo I don't know if you're the one behind either of these forks, but I managed to fix the error on my end, but did so in vendor file - dunno if you or another will PR this after you validate the fix.

edit: currently there's a duplication bug for children - working on it

synsyst avatar Aug 26 '25 14:08 synsyst

I've added your code. Can you check?

Casmo avatar Aug 26 '25 14:08 Casmo

I've added your code. Can you check?

it's not ready at the moment - it duplicates children when saving for some reason, I've got another 15minutes today, otherwise I'll look at it tomorrow.

Image

synsyst avatar Aug 26 '25 14:08 synsyst

@Casmo I assume you're repo owner or something on backstagephp - I've added a PR on HasRelations.php there and added the same description as here as my work. I've unit tested this somewhat and it seems to work now for me - parents and children are created and saved correctly - no errors on my end and no duplicates

Fix: Enhanced caching for hierarchical relationships in AdjacencyList component

Root Cause Analysis

The AdjacencyList component's getCachedExistingRecords() method only caches direct query results but ignores eager-loaded children from hierarchical relationships loaded via with('children'). This means:

  1. Parent records are cached correctly from the initial query
  2. Child records exist in memory (eager-loaded) but aren't cached
  3. During save, children return null from cache lookup → HasOneOrMany::save() error
  4. Original null handling saves records immediately, causing duplicates in hierarchical structures

Solution

Enhanced caching that flattens all eager-loaded hierarchical data into cache, plus improved save logic to prevent duplicates.

Implementation

1. Add Enhanced Caching Methods

Add these two methods after the existing getCachedExistingRecords() method:

/**
 * Get cached records including all children from eager-loaded relationships
 * 
 * ROOT CAUSE FIX: The original getCachedExistingRecords() only caches direct query results
 * but ignores eager-loaded children from with('children'). For hierarchical relationships,
 * this means children exist in memory but aren't cached, causing null lookups and 
 * duplicate saves. This method flattens all eager-loaded hierarchical data into cache.
 */
public function getCachedExistingRecordsWithChildren(): Collection
{
    $cache = $this->getCachedExistingRecords();
    $childrenKey = $this->getChildrenKey();
    
    // Recursively flatten all children from eager-loaded relationships into cache
    $this->flattenChildrenIntoCache($cache, $cache->values(), $childrenKey);
    
    return $cache;
}

/**
 * Recursively traverse eager-loaded children and add them to cache
 */
private function flattenChildrenIntoCache(Collection $cache, Collection $records, string $childrenKey): void
{
    foreach ($records as $record) {
        if ($record->relationLoaded($childrenKey)) {
            $children = $record->{$childrenKey};
            
            foreach ($children as $child) {
                $childKey = md5('record-' . $child->getKey());
                if (!$cache->has($childKey)) {
                    $cache->put($childKey, $child);
                }
            }
            
            // Recursively process children's children
            if ($children->isNotEmpty()) {
                $this->flattenChildrenIntoCache($cache, $children, $childrenKey);
            }
        }
    }
}

2. Update saveRelationshipsUsing Callback

In the saveRelationshipsUsing callback, change:

// FROM:
$cachedExistingRecords = $component->getCachedExistingRecords();

// TO:
$cachedExistingRecords = $component->getCachedExistingRecordsWithChildren();

3. Update Null Record Handling

Replace the existing null record handling in the $traverse function:

// REPLACE the existing null record block with:
/*
 * ROOT CAUSE FIXED: Handle records that don't exist in cache
 * 
 * Problem: Original getCachedExistingRecords() only cached root-level records,
 * ignoring eager-loaded children from hierarchical relationships. This caused:
 * 1. Children weren't found in cache during save → null record error
 * 2. Fallback database lookups and duplicate saves
 * 
 * Solution: Now using getCachedExistingRecordsWithChildren() which flattens
 * all eager-loaded hierarchical data into cache, eliminating the cache misses.
 * 
 * Key insight: The save process has two distinct responsibilities:
 * - Recursive $traverse calls save via main project relationship (sets project_id)
 * - Later saveMany call establishes task hierarchy (sets parent_task_id)
 * Both are needed: traverse for project membership, saveMany for parent-child relationships
 */
if ($record === null) {
    // This should rarely happen now with improved caching, but handle new records
    $modelData = collect($item)->except($childrenKey)->toArray();
    $model = $relationship->getRelated();
    $record = new $model();
    $record->fill($modelData);
    
    Log::info('AdjacencyList: Created new record (cache miss)', [
        'itemKey' => $itemKey,
        'modelData' => $modelData,
        'model_class' => get_class($record)
    ]);
    
    $cachedExistingRecords->put($itemKey, $record);
}

4. Update saveMany Logic for Hierarchical Relationships

Replace the existing saveMany call in the children processing section:

// REPLACE:
$record->{$childrenKey}()->saveMany($childrenRecords);

// WITH:
// Filter out children that already have the correct parent_task_id set
// to avoid duplicates while ensuring parent-child relationships are established
$unsavedChildren = $childrenRecords->filter(function ($child) use ($record) {
    return !$child->exists || $child->parent_task_id !== $record->id;
});

if ($unsavedChildren->isNotEmpty()) {
    $record->{$childrenKey}()->saveMany($unsavedChildren);
}

Benefits

  • Eliminates cache misses - All hierarchical records properly cached
  • Prevents duplicate saves - Filtered saveMany logic only saves what's needed
  • Maintains compatibility - Original method unchanged for other features
  • Diagnostic logging - Cache miss logging helps identify any remaining issues

This approach transforms eager-loaded hierarchical data that was already in memory but ignored by the cache, into a properly indexed cache collection, while adding smart filtering to prevent duplicate relationship saves.

synsyst avatar Aug 27 '25 11:08 synsyst

New bug I'm finding is in the reordering - dragging stuff around I got this:

Trying to access array offset on null

Guess that's something I'll be looking into as well. I'll look into sort - not sure it's critical for our use case, so let me see if I touch upon it, but it seems wonky at the moment. Perhaps I missed something

synsyst avatar Aug 27 '25 11:08 synsyst

I can't see you PR, I'll merge it to the backstage repository.

Casmo avatar Aug 29 '25 08:08 Casmo

Something else worth mentioning: I had a look at the branch history and it seems that somewhere between 3.2.1 and 3.2.2, a large chunk of commit history was lost.

Currently, the main branch only has 68 commits and the tag for v3.2.2 has 64 commits. The history within the branch and tag implies no commits were made between 1st Jan 2024 and 4th March 2024.

However, the tag for 3.2.1 has 105 commits. Additionally, it has an additional 28 commits just within the month of February. These commits include the functionality for relationships.

My assumption is that the branches were diverged at some point during the development of 3.2.1 and 3.2.2, and they were never re-merged, causing the changes to get lost. Possibly during the development of the v4 beta?

@saade Is there a chance that these 2 branch histories could be re-merged for a fix?

aSeriousDeveloper avatar Sep 05 '25 10:09 aSeriousDeveloper

@aSeriousDeveloper thanks for digging into this — I think you’re probably right.

From my perspective, the 4.x-beta could actually be released as 3.3 since the breaking changes are fairly minimal: • Asset registration: you now need to use a custom theme for the plugin to work. Docs here • Click behavior: items are no longer editable by default. You’ll need to set recordAction('edit') to restore the previous behavior.

Re-analyzing and merging the two divergent branch histories might introduce even more errors at this point.

That way, V4 could then arrive aligned with Filament v4 support, along with the more significant breaking changes.

What do you guys think? (please up/down vote this comment)

saade avatar Sep 06 '25 00:09 saade

Sounds good, I remember that the 3.2.1 wasn't compatible with a Filament (3.x) version and people had to upgrade, resulting in the relationship issues.

For Filament 3.x users the fix would be nice, but they may use the backstagephp repository (or a separate branch here) until they upgrade Filament to 4.x.

More here.

Casmo avatar Sep 08 '25 09:09 Casmo