archtechx / tenancy

Automatic multi-tenancy for Laravel. No code changes needed.
https://tenancyforlaravel.com
MIT License
3.56k stars 424 forks source link

`morphOne` Relationship Not Loading #1247

Open dipaksarkar opened 2 weeks ago

dipaksarkar commented 2 weeks ago

Bug description

I am experiencing an issue with the stancl/tenancy package where the morphOne relationship does not load as expected, while the morphMany relationship works correctly. This issue occurs when the morphOne relationship is used in conjunction with the stancl/tenancy package.

Code Example

Here is a simplified version of the Admin model that illustrates the problem:

<?php

namespace App\Models;

use Coderstm\Models\Log;
use Illuminate\Foundation\Auth\User as Authenticatable;

class Admin extends Authenticatable
{
    // Working correctly
    public function logs()
    {
        return $this->morphMany(Log::class, 'logable');
    }

    // Not Loading with tenancy package
    public function lastLogin()
    {
        return $this->morphOne(Log::class, 'logable')
                    ->where('type', 'login')
                    ->latestOfMany(); 
    }
}

In the example above:

Steps to reproduce

  1. Set up a Laravel project with the stancl/tenancy package.
  2. Define the Admin model with the morphMany and morphOne relationships as shown in the code example above.
  3. Attempt to load the lastLogin relationship in a tenant context.

Expected behavior

The lastLogin relationship should load the latest Log record of type login for the Admin model, similar to how the logs relationship loads the associated Log records.

Laravel version

11.x

stancl/tenancy version

3.8

stancl commented 2 weeks ago

If you're closing a bug report, you should mention why you're closing it.

dipaksarkar commented 2 weeks ago

@stancl, I’ve been investigating the issue further. It was working initially, but the problem has resurfaced. I found that the lastLogin relationship works when I remove the ->where('type', 'login') condition. However, with the condition in place, it still doesn't load, so the issue persists. I’ll need to reopen this. :(

<?php

namespace App\Models;

use Coderstm\Models\Log;
use Illuminate\Foundation\Auth\User as Authenticatable;

class Admin extends Authenticatable
{
    // Working correctly
    public function logs()
    {
        return $this->morphMany(Log::class, 'logable');
    }

    // Not Loading with tenancy package
    public function lastLogin()
    {
        return $this->morphOne(Log::class, 'logable')
                    ->where('type', 'login') // works without this condition
                    ->latestOfMany(); 
    }
}
dipaksarkar commented 2 weeks ago

@stancl I tried another solution, but it’s not working as expected. Both are working without tenancy package.

<?php

namespace App\Models\Tenant;

use Coderstm\Models\Log;
use App\Models\Central\User;
use Illuminate\Support\Facades\DB;
use App\Tenancy\Contracts\Syncable;
use Coderstm\Models\Admin as Model;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Relations\MorphMany;
use Stancl\Tenancy\Database\Concerns\ResourceSyncing;

class Admin extends Model implements Syncable
{
    use ResourceSyncing;

    protected $guarded = [];

    protected $fillable = [
        'first_name',
        'last_name',
        'email',
        'status',
        'password',
        'phone_number',
        'is_supper_admin',
        'is_instructor',
        'is_active',
        'global_id',
        'rfid',
    ];

    protected $appends = [
        'name',
        'guard',
    ];

    protected $casts = [
        'email_verified_at' => 'datetime',
        'is_active' => 'boolean',
        'is_instructor' => 'boolean',
        'is_supper_admin' => 'boolean',
    ];

    public function logs(): MorphMany
    {
        return $this->morphMany(Log::class, 'logable');
    }

    public function getGlobalIdentifierKey()
    {
        return $this->getAttribute($this->getGlobalIdentifierKeyName());
    }

    public function getGlobalIdentifierKeyName(): string
    {
        return 'global_id';
    }

    public function getCentralModelName(): string
    {
        return User::class;
    }

    public function getCentralModelFillable(): array
    {
        return (new User)->getFillable();
    }

    public function getSyncedAttributeNames(): array
    {
        return [
            'first_name',
            'last_name',
            'email',
            'password',
        ];
    }

    public static function booted()
    {
        parent::booted();

        static::addGlobalScope('last_login_at', function (Builder $builder) {
            $builder->withCount([
                'logs as last_login_at' => function (Builder $query) {
                    $query->select(DB::raw("MAX(created_at) as max_created_at"))->whereType('login');
                },
            ]);
        });
    }
}
dipaksarkar commented 2 weeks ago

It's working when used orderBy('created_at', 'desc' instead of latestOfMany()

public function lastLogin(): MorphOne
{
    return $this->morphOne(Log::class, 'logable')
        ->where('type', 'login')
        ->orderBy('created_at', 'desc'); // change latestOfMany();
}
stancl commented 4 days ago

Would need more information to know how exactly this is related to the package.