staudenmeir / eloquent-has-many-deep

Laravel Eloquent HasManyThrough relationships with unlimited levels
MIT License
2.67k stars 157 forks source link

$post->country relation #179

Closed yakoffka closed 1 year ago

yakoffka commented 1 year ago

Hello. Thank you for a wonderful and necessary package. But I can't figure out how to get the $post->country relation:

Consider this example from the Laravel documentation with an additional level: Country → has many → User → has many → Post → has many → Comment

class Post extends Model
{
    public function country()
    {
        return $this->hasOneDeepFromReverse((new User())->country());
    }
}

return error: Expected parameter of type '\Staudenmeir\EloquentHasManyDeep\HasManyDeep', '\Illuminate\Database\Eloquent\Relations\BelongsTo' provided

Can you please tell me how to describe this relationship?

staudenmeir commented 1 year ago

Hi @yakoffka, Please provide the whole exception with its stacktrace. How are you using the relationship when you get this error?

yakoffka commented 1 year ago

Strictly speaking, I have a slightly different relations: User{id} → has many → ChatGroup{uuid, user_id} → has many → Chat{uuid, chat_group_uuid} → has many → Post{uuid, chat_uuid}

I was able to describe all connections except $chat->user(). The error occurs in tinker:

TypeError: App\Models\Chats\Chat::hasOneDeepFromReverse(): Argument #1 ($relation) must be of type Staudenmeir\EloquentHasManyDeep\HasManyDeep, Illuminate\Database\Eloquent\Relations\BelongsTo given, called in /var/www/app/Models/Chats/Chat.php on line 137

on 137 line return $this->hasOneDeepFromReverse((new ChatGroup())->user());

(new ChatGroup())->user() return BelongsTo

But, as far as I understand, the receipt scheme is similar. How to get the relation in your example for $post->country()? Is the example that I described in the first post working?

yakoffka commented 1 year ago
yakoffka commented 1 year ago

App\Models\Chats\Chat::hasOneDeepFromReverse(): Argument #1 ($relation) must be of type Staudenmeir\EloquentHasManyDeep\HasManyDeep, Illuminate\Database\Eloquent\Relations\BelongsTo given, called in /var/www/app/Models/Chats/Chat.php on line 137 {"exception":"[object] (TypeError(code: 0): App\Models\Chats\Chat::hasOneDeepFromReverse(): Argument #1 ($relation) must be of type Staudenmeir\EloquentHasManyDeep\HasManyDeep, Illuminate\Database\Eloquent\Relations\BelongsTo given, called in /var/www/app/Models/Chats/Chat.php on line 137 at /var/www/vendor/staudenmeir/eloquent-has-many-deep/src/Eloquent/Traits/ReversesRelationships.php:29) [stacktrace]

0 /var/www/app/Models/Chats/Chat.php(137): App\Models\Chats\Chat->hasOneDeepFromReverse(Object(Illuminate\Database\Eloquent\Relations\BelongsTo))

1 /var/www/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php(576): App\Models\Chats\Chat->user()

2 /var/www/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php(528): Illuminate\Database\Eloquent\Model->getRelationshipFromMethod('user')

3 /var/www/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php(453): Illuminate\Database\Eloquent\Model->getRelationValue('user')

4 /var/www/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Model.php(2218): Illuminate\Database\Eloquent\Model->getAttribute('user')

5 /var/www/vendor/psy/psysh/src/ExecutionLoopClosure.php(55) : eval()'d code(3): Illuminate\Database\Eloquent\Model->__get('user')

6 /var/www/vendor/psy/psysh/src/ExecutionLoopClosure.php(55): eval()

7 /var/www/vendor/psy/psysh/src/ExecutionClosure.php(89): Psy\{closure}()

8 /var/www/vendor/psy/psysh/src/Shell.php(383): Psy\ExecutionClosure->execute()

9 /var/www/vendor/psy/psysh/src/Shell.php(354): Psy\Shell->doInteractiveRun()

10 /var/www/vendor/symfony/console/Application.php(171): Psy\Shell->doRun(Object(Symfony\Component\Console\Input\ArrayInput), Object(Psy\Output\ShellOutput))

11 /var/www/vendor/psy/psysh/src/Shell.php(329): Symfony\Component\Console\Application->run(Object(Symfony\Component\Console\Input\ArrayInput), Object(Psy\Output\ShellOutput))

12 /var/www/vendor/laravel/tinker/src/Console/TinkerCommand.php(81): Psy\Shell->run()

13 /var/www/vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php(36): Laravel\Tinker\Console\TinkerCommand->handle()

14 /var/www/vendor/laravel/framework/src/Illuminate/Container/Util.php(41): Illuminate\Container\BoundMethod::Illuminate\Container\{closure}()

15 /var/www/vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php(93): Illuminate\Container\Util::unwrapIfClosure(Object(Closure))

16 /var/www/vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php(37): Illuminate\Container\BoundMethod::callBoundMethod(Object(Illuminate\Foundation\Application), Array, Object(Closure))

17 /var/www/vendor/laravel/framework/src/Illuminate/Container/Container.php(651): Illuminate\Container\BoundMethod::call(Object(Illuminate\Foundation\Application), Array, Array, NULL)

18 /var/www/vendor/laravel/framework/src/Illuminate/Console/Command.php(178): Illuminate\Container\Container->call(Array)

19 /var/www/vendor/symfony/console/Command/Command.php(308): Illuminate\Console\Command->execute(Object(Symfony\Component\Console\Input\ArgvInput), Object(Illuminate\Console\OutputStyle))

20 /var/www/vendor/laravel/framework/src/Illuminate/Console/Command.php(148): Symfony\Component\Console\Command\Command->run(Object(Symfony\Component\Console\Input\ArgvInput), Object(Illuminate\Console\OutputStyle))

21 /var/www/vendor/symfony/console/Application.php(1014): Illuminate\Console\Command->run(Object(Symfony\Component\Console\Input\ArgvInput), Object(Symfony\Component\Console\Output\ConsoleOutput))

22 /var/www/vendor/symfony/console/Application.php(301): Symfony\Component\Console\Application->doRunCommand(Object(Laravel\Tinker\Console\TinkerCommand), Object(Symfony\Component\Console\Input\ArgvInput), Object(Symfony\Component\Console\Output\ConsoleOutput))

23 /var/www/vendor/symfony/console/Application.php(171): Symfony\Component\Console\Application->doRun(Object(Symfony\Component\Console\Input\ArgvInput), Object(Symfony\Component\Console\Output\ConsoleOutput))

24 /var/www/vendor/laravel/framework/src/Illuminate/Console/Application.php(102): Symfony\Component\Console\Application->run(Object(Symfony\Component\Console\Input\ArgvInput), Object(Symfony\Component\Console\Output\ConsoleOutput))

25 /var/www/vendor/laravel/framework/src/Illuminate/Foundation/Console/Kernel.php(155): Illuminate\Console\Application->run(Object(Symfony\Component\Console\Input\ArgvInput), Object(Symfony\Component\Console\Output\ConsoleOutput))

26 /var/www/artisan(37): Illuminate\Foundation\Console\Kernel->handle(Object(Symfony\Component\Console\Input\ArgvInput), Object(Symfony\Component\Console\Output\ConsoleOutput))

27 {main}

"}

staudenmeir commented 1 year ago

/var/www/app/Models/Chats/Chat.php on line 137

What exactly does this line look like? It sounds like you defined a (incorrect) return type ...(): BelongsTo.

yakoffka commented 1 year ago

BelongsTo defined in ChatGroup model:

class ChatGroup extends Model
{
    ...

    /**
     * @return HasManyDeep
     * см. https://github.com/staudenmeir/eloquent-has-many-deep
     */
    public function posts(): HasManyDeep
    {
        return $this->hasManyDeep(Post::class, [Chat::class]);
    }

    /**
     * @return BelongsTo
     */
    public function user(): BelongsTo
    {
        return $this->belongsTo(User::class);
    }

    ...
}

And here is the code from the model Chat:


class Chat extends Model
{
    ...

    /**
     * @return BelongsTo
     */
    public function group(): BelongsTo
    {
        return $this->belongsTo(
            ChatGroup::class,
            'chat_group_uuid',
            'uuid'
        );
    }

    /**
     * @return HasMany
     */
    public function posts(): HasMany
    {
        return $this->hasMany(
            Post::class,
            'chat_uuid',
            'uuid'
        );
    }

    /**
     * User → has many → ChatGroup → has many → Chat → has many → Post
     */
    public function user()
    {
         return $this->hasOneDeepFromReverse((new ChatGroup())->user()); // line 137
    }

    ...
}
yakoffka commented 1 year ago

in the User model, the inverse relationship is defined

class User extends Model
{
    ...

    /**
     * @return HasMany
     */
    public function chatGroups(): HasMany
    {
        return $this->hasMany(
            ChatGroup::class,
            'user_id',
            'id',
        );
    }

    ...
}
yakoffka commented 1 year ago

Argument #1 ($chatGroup->user()) for $chat->hasOneDeepFromReverse() and truth has a type BelongsTo, but it must be of this type, since this is the inverse relation of HasMany ($user->chatGroup())

yakoffka commented 1 year ago

I would just like to see a working example of getting a relation $post->country for your example (Country → has many → User → has many → Post → has many → Comment).

class Post extends Model
{
    public function country()
    {
        return $this->hasOneDeepFromReverse((new User())->country());
    }
}

If you get this connection without any problems, then the error is hiding somewhere in my code.

yakoffka commented 1 year ago

I think I figured it out: in this case, you need to use the BelongsToThrough relation

yakoffka commented 1 year ago

Indeed the BelongsToThrough relation helped. I suffered a little with the indication of custom keys, but it all worked.

class Chat extends Model
{

    /**
     * Пользователь, которому принадлежит группа данного чата (Chat -> User)
     *
     * User{id} → has many → ChatGroup{uuid, user_id}
     *      → has many → Chat{uuid, chat_group_uuid}
     *      → has many → Post{uuid, chat_uuid}t
     */
    public function user(): BelongsToThrough
    {
        return $this->belongsToThrough(
            User::class,
            ChatGroup::class,
            'chat_group_uuid', // локальный ключ в таблице chats
            null,
            [
                ChatGroup::class => 'chat_group_uuid', // внешний ключ в таблице chats
                User::class => 'user_id', // внешний ключ в таблице chat_groups
            ]
        );
    }   
 }

Thanks again for the package!