0

I'm trying to get a many-to-may relationship to work, but I can't get it to work.

I have a database with 3 tables: users, favorites & videos. The favorites table contains 2 foreign keys that point to the users table and the videos table.

Currently I have the following:

User Model:

public function getVideos()
{
    return $this->hasMany(Video::class, ['id' => 'video_id'])->viaTable(UserFavorites::tableName(), ['favorite_id' => 'id']);
}

Video Model:

public function getUsers()
{
    return $this->hasMany(User::class, ['id' => 'user_id'])->via(UserFavorites::tableName());
}

VideoController: (ActiveController)

public function actions()
{
    $actions = parent::actions();
    $actions['index']['prepareDataProvider'] = [$this, 'prepareDataProvider'];
    return $actions;
}

public function prepareDataProvider()
{
    $user = User::findOne('1=1');
    return new ActiveDataProvider([
       'query' => $user->getVideos()
    ]);
}

Following what they did in: Many-to-many relation in yii2 activedataprovider
but that did not work. I also tried with extraFields and expand with no luck.

How do I make it so the result will look sorta like this:

[{
    "id": 1, 
    "name": "video name",
    "likes": 69,
    "user": [{
        "id": 1,
        "name": "John"
    }]
},
...
]

1 Answers1

0

You need to update the code in a few places.

The REST active controller.

It looks like you are trying to get one video with the user's that liked it, you should be using the view action, not the index action, that should be used to get a list of videos, you can use the base view action, no need to override it, it will work like you need it.

In the controller, should be enough with setting the model.

use yii\rest\ActiveController;

class VideoController extends ActiveController
{    
    public $modelClass = Video::class;
}

Consider setting $_verbs and actionOptions if you need them to not be the default.

The model.

You should use extraFields to expose the method as a field.

use yii\db\ActiveRecord;

class Video extends ActiveRecord
{
    // Other methods here...

    public function getUsers()
    {
        return $this->hasMany(User::class, ['id' => 'user_id'])
            ->viaTable(UserFavorites::tableName(), [
                'video_id' => 'id'
            ]);
    }

    public function extraFields()
    {
        return [
            'users'
        ];
    }
}

If you want to use the same name for the API call as the function, i.e. the API call uses expand=users and the method is called getUsers() you can use the users shortcut on expandFields, if the names are different, you can use the expanded format, i.e. 'likes' => 'users'

Then, querying your API with:

https://apiv1.example.com/videos/45?expand=users

Should give you the expected results like in your post

[{
    "id": 1, 
    "name": "video name",
    "likes": 69,
    "users": [{
        "id": 1,
        "name": "John"
    }]
},
...
]

Notice that the array of users will be called 'users' and not 'user' if you configured the same way, you can change that using the expanded notation inside extraFields.

Raul Sauco
  • 2,645
  • 3
  • 19
  • 22
  • Thank you for explaining how the dots are connected; I changed the method name to `getUsers` and the extra field name to `users`. I'm now getting a "has no relation named ...' error. – NS - Not the train Dec 10 '20 at 11:19
  • I just noticed you are using `via` but giving it a table name instead of a relation name. Check the documentation for that on the [Yii docs](https://www.yiiframework.com/doc/api/2.0/yii-db-activerelationtrait#via()-detail) One way to fix it would be to use `viaTable` I will update the answer. – Raul Sauco Dec 10 '20 at 13:23