Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CountCache on BelongsToMany #106

Open
Xety opened this issue Jul 18, 2023 · 12 comments
Open

CountCache on BelongsToMany #106

Xety opened this issue Jul 18, 2023 · 12 comments

Comments

@Xety
Copy link

Xety commented Jul 18, 2023

Hello,

Is there a way to do a countcache with a BelongsToMany relationship with a pivot table ? There's no info in the doc

@kirkbushell
Copy link
Owner

Not yet, but I think it'll be possible with version 11.

@kirkbushell kirkbushell added this to the 11.0 milestone Aug 26, 2023
@kirkbushell
Copy link
Owner

@Xety took another look at this, this is going to be considerably more difficult than I thought. I'll pen it for an 11.1 release and work on it in the background. I think I need to essentially create solutions for each type of relation, rather than eloquence feature.

@Xety
Copy link
Author

Xety commented Dec 7, 2023

Hey,
I have tested some things about this and i found something interesting. I'm using a Model Pivot like this one :

<?php

namespace App\Models;

use Eloquence\Behaviours\CountCache\Countable;
use Illuminate\Database\Eloquent\Relations\Pivot;

class MaterialPart extends Pivot
{
    use Countable;

    /**
     * Return the count cache configuration.
     *
     * @return array
     */
    public function countCaches(): array
    {
        return [
            [
                'model'      => Material::class,
                'field'      => 'part_count',
                'foreignKey' => 'material_id',
                'key'        => 'id'
            ],
            [
                'model'      => Part::class,
                'field'      => 'material_count',
                'foreignKey' => 'part_id',
                'key'        => 'id'
            ]
        ];
    }

}

So my Materials can have many Parts, and a Parts can have many Materials (belongsToMany).
In Material model :

/**
 * Get the parts for the material.
 *
 * @return BelongsToMany
 */
public function parts(): BelongsToMany
{
    return $this->belongsToMany(Part::class)
        ->using(MaterialPart::class);
}

In Part Model :

/**
 * Get the materials for the part.
 *
 * @return BelongsToMany
 */
public function materials(): BelongsToMany
{
    return $this->belongsToMany(Material::class)
        ->using(MaterialPart::class);
}

With that config, it work pretty well when adding a row; when i create a Part, with 2 selected Materials, both Materials has 1 Part each, and the Part has 2 Materials. If I add another Part with the same materials the countcache work pretty well :

My Parts
Capture d'écran 2023-12-07 224118
My Materials
Capture d'écran 2023-12-07 224250

My Pivot Table
Capture d'écran 2023-12-07 224301

The problem come when you delete a Part or a Material, the countcache isn't updated.
Exemple, if I delete the Part test2, the Part count of the material BMP1 won't be updated and will stay at 2 instead of 1. I think there's something wrong about the observer when deleting (event not triggered ?)

EDIT : I found the solution (maybe not the most optimized, but it work)

I have setup an Observer (and registered them) for each Model :

<?php

namespace App\Observers;

use Illuminate\Support\Facades\Auth;
use App\Models\Part;

class PartObserver
{
    public function deleting(Part $part): void
    {
        $materials = $part->materials;

        foreach ($materials as $material) {
            $material->parts()->detach($part->getKey());
        }

    }
}



<?php

namespace App\Observers;

use Illuminate\Support\Facades\Auth;
use App\Models\Material;

class MaterialObserver
{
    public function deleting(Material $material): void
    {
        $parts = $material->parts;

        foreach ($parts as $part) {
            $part->materials()->detach($material->getKey());
        }

    }
}

So each time you delete a Part, the event deleting will be trrigered and for each material that was assigned to the Part, you detach them from the Part and it will trigger the countcache. Of course it work also when you delete a Material.
This is insane wtf.

@kirkbushell
Copy link
Owner

@Xety I've just released v11 - did you want to test and see if it will work "out of the box"? V11 works directly with relationship objects, making it much easier to do aggregate counts.

@kirkbushell
Copy link
Owner

@Xety - see comment above.

@Xety
Copy link
Author

Xety commented May 18, 2024

@Xety - see comment above.

Hey, sorry I was very busy in the last couple of weeks. I will try it soon.

@kirkbushell
Copy link
Owner

@Xety - see comment above.

Hey, sorry I was very busy in the last couple of weeks. I will try it soon.

All good - did you get a chance?

@Xety
Copy link
Author

Xety commented Jun 18, 2024

@kirkbushell Hey, sorry for the late reply, I was busy IRL and needed to upgrade to Laravel11 too to test your package, so it required some work.

Well, I have tested it and it seems like I found an alternative to the old system. The only thing i changed was in the Pivot model.

In Pivot MaterialPart.php

<?php

namespace BDS\Models;

use Eloquence\Behaviours\CountCache\CountedBy;
use Eloquence\Behaviours\CountCache\HasCounts;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\Pivot;

class MaterialPart extends Pivot
{
    use HasCounts;
    
    // BEFORE
    /**
     * Return the count cache configuration.
     *
     * @return array
     */
    /*public function countCaches(): array
    {
        return [
            [
                'model'      => Material::class,
                'field'      => 'part_count',
                'foreignKey' => 'material_id',
                'key'        => 'id'
            ],
            [
                'model'      => Part::class,
                'field'      => 'material_count',
                'foreignKey' => 'part_id',
                'key'        => 'id'
            ]
        ];
    }*/
    
    // AFTER
    #[CountedBy(as: 'part_count')]
    public function material(): BelongsTo
    {
        return $this->belongsTo(Material::class);
    }

    #[CountedBy(as: 'material_count')]
    public function part(): BelongsTo
    {
        return $this->belongsTo(Part::class);
    }
}

In model Part.php

public function materials(): BelongsToMany
{
    return $this->belongsToMany(Material::class)
        ->using(MaterialPart::class)
        ->withTimestamps();
}

In model Material.php

public function parts(): BelongsToMany
{
    return $this->belongsToMany(Part::class)
        ->using(MaterialPart::class);
}

I still have my observers too :

MaterialObserver.php

<?php

namespace BDS\Observers;

use BDS\Models\Material;

class MaterialObserver
{
    /**
     * Handle the "deleting" event.
     */
    public function deleting(Material $material): void
    {
        $parts = $material->parts;

        foreach ($parts as $part) {
            $part->materials()->detach($material->getKey());
        }
    }
}

MaterialObserver.php

<?php

namespace BDS\Observers;

use BDS\Models\Part;

class PartObserver
{
    /**
     * Handle the "deleting" event.
     */
    public function deleting(Part $part): void
    {
        $materials = $part->materials;

        foreach ($materials as $material) {
            $material->parts()->detach($part->getKey());
        }
    }
}

Looks like everythings work well again, if you can make some more tests to confirm it.

@kirkbushell
Copy link
Owner

@Xety Sorry, I require clarification - are you saying the count cache is working even with BelongsToMany relationships? Are changes required as part of Eloquence?

@Xety
Copy link
Author

Xety commented Jun 27, 2024

@kirkbushell With this setup, yes count cache is working with BelongsToMany relationships.
No change required if you setup a Pivot model with 2 belongsTo relationships.

@kirkbushell
Copy link
Owner

@kirkbushell With this setup, yes count cache is working with BelongsToMany relationships. No change required if you setup a Pivot model with 2 belongsTo relationships.

Ah, right okay - so yeah some work needs to be done on Eloquence to support that. Thanks :)

@Xety
Copy link
Author

Xety commented Jun 27, 2024

@kirkbushell Well yeah, the best way would be to have something like that without Pivot model :

#[CountedBy(as: 'part_count')]
public function materials(): BelongsToMany
{
    return $this->belongsToMany(Material::class);
}

#[CountedBy(as: 'material_count')]
public function parts(): BelongsToMany
{
    return $this->belongsToMany(Part::class);
}

But at least i have something working until the official support.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants