-1

I am currently working on the creation of an API using Lumen. I have, for the example, 2 tables users and users_token with the corresponding models User and UsersToken:

App\User:

<?php

namespace App;

use Illuminate\Auth\Authenticatable;
use Illuminate\Contracts\Auth\Access\Authorizable as AuthorizableContract;
use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract;
use Illuminate\Database\Eloquent\Model;
use Laravel\Lumen\Auth\Authorizable;

class User extends Model implements AuthenticatableContract, AuthorizableContract
{
    use Authenticatable, Authorizable;

    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = [
        'name',
        'email',
        'firstname',
        'password'
    ];

    /**
     * The attributes excluded from the model's JSON form.
     *
     * @var array
     */
    protected $hidden = [
        'id',
        'password',
        'email_verified_at'
    ];


    public function tokens()
    {
        return $this->hasMany('App\UsersToken', 'user_id');
    }
}

App\UsersToken:

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class UsersToken extends Model
{
    /**
     * The table associated with the model.
     *
     * @var string
     */
    protected $table = 'users_token';

    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = [
        'user_id',
        'token',
    ];
}

The table users_token has 2 importants fields: user_id (foreign key linked with id field from users table) and token (a simple string).

In my model User, I have the method tokens that return all the tokens from the user using the hasMany() function:

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    public function tokens()
    {
        return $this->hasMany('App\UsersToken');
    }
}

The table users_token also contains the defaults fields created_at and updated_at. So to get the oldest modified token of an user, I decide to use the oldest() and first() functions of Laravel:

$latestUserToken = $user->tokens->oldest('updated_at')->first();

So then I just have to update the value token of it and finally save() it:

$latestUserToken = $user->tokens->oldest('updated_at')->first();
$latestUserToken->token = 'test';
$latestUserToken->save();

But sadly I get this error message:

SQLSTATE[42S22]: Column not found: 1054 Unknown column 'id' in 'where clause' (SQL: update users_token set token = test, users_token.updated_at = 2020-04-06 17:34:59 where id is null)

If I make a print_r of $user->tokens->oldest('updated_at')->first() I get this:

[withCount:protected] => Array
        (
        )

    [perPage:protected] => 15
    [exists] => 1
    [wasRecentlyCreated] => 
    [attributes:protected] => Array
        (
            [user_id] => 11
            [token] => IsSdh03fKS3NCpfVmfm8XZyK1uYf2hSb3nRkojo86XRN7zdZnRqEOZe2HvXT
            [created_at] => 2020-04-05 17:18:22
            [updated_at] => 2020-04-01 17:18:22
        )

    [original:protected] => Array
        (
            [user_id] => 11
            [token] => IsSdh03fKS3NCpfVmfm8XZyK1uYf2hSb3nRkojo86XRN7zdZnRqEOZe2HvXT
            [created_at] => 2020-04-05 17:18:22
            [updated_at] => 2020-04-01 17:18:22
        )

    [changes:protected] => Array
        (
        )

    [casts:protected] => Array
        (
        )

    [classCastCache:protected] => Array
        (
        )

    [dates:protected] => Array
        (
        )

    [dateFormat:protected] => 
    [appends:protected] => Array
        (
        )

    [dispatchesEvents:protected] => Array
        (
        )

    [observables:protected] => Array
        (
        )

    [relations:protected] => Array
        (
        )

    [touches:protected] => Array
        (
        )

    [timestamps] => 1
    [hidden:protected] => Array
        (
        )

    [visible:protected] => Array
        (
        )

    [guarded:protected] => Array
        (
            [0] => *
        )

)

Have someone an idea where my error is?

johannchopin
  • 13,720
  • 10
  • 55
  • 101

3 Answers3

2

Could you review printing

$latestUserToken = $user->tokens->oldest('updated_at')->first();

I think that is a hybrid between user and user_token so isn't a clear instance to update.

Also you can review users_token table structure (primary key).

JsMoreno
  • 93
  • 1
  • 6
  • Interesting question actually I don't have defined the primary key of `UsersToken` because it's the composite of the field `user_id` and `token` – johannchopin Apr 08 '20 at 19:49
  • I found this https://stackoverflow.com/a/49931126/13112515 or simply consider add a primary key. – JsMoreno Apr 08 '20 at 20:07
  • Yes using a simple primary key like the field `token` work well. If you write this answer with an example I will validate your answer – johannchopin Apr 08 '20 at 20:11
1

Eloquent doesn't support composite primary keys.

if you want to reach that you should override two methods and override $primary_key attribute.

in App\UsersToken:

class UsersToken extends Model
{
    /**
     * The table associated with the model.
     *
     * @var string
     */
    protected $table = 'users_token';

    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = [
        'user_id',
        'token',
    ];

    protected $primaryKey = [
        'user_id',
        'token',
    ];

    /**
     * Set the keys for a save update query.
     *
     * @param  \Illuminate\Database\Eloquent\Builder  $query
     * @return \Illuminate\Database\Eloquent\Builder
     */
    protected function setKeysForSaveQuery(\Illuminate\Database\Eloquent\Builder $query)
    {
        $keys = $this->getKeyName();
        if (! is_array($keys)) {
            return parent::setKeysForSaveQuery($query);
        }

        foreach ($keys as $keyName) {
            $query->where($keyName, '=', $this->getKeyForSaveQuery($keyName));
        }

        return $query;
    }

    /**
     * Get the primary key value for a save query.
     *
     * @param  mixed  $keyName
     * @return mixed
     */
    protected function getKeyForSaveQuery($keyName = null)
    {
        if (null === $keyName) {
            $keyName = $this->getKeyName();
        }

        return $this->original[$keyName] ?? $this->getAttribute($keyName);
    }
}
AH.Pooladvand
  • 1,944
  • 2
  • 12
  • 26
  • Thanks for the tips, I think I already read something similar here https://stackoverflow.com/questions/31415213/how-i-can-put-composite-keys-in-models-in-laravel-5. But I think I will stay with the use of a single primary key – johannchopin Apr 08 '20 at 20:49
1

I tried many different ways to generate the error in my workspace. The only time I could generate it is not having the id column in users_token table. And it looks like there's no id column in users_token table.

If not defined explicitly, Eloquent will look for the field id as the primary key for any table. You can define the primary key field name on model using protected $primaryKey = 'token';. I'm assuming you want the field token as the primary key.

But I would recommend not to use token as the primary. Use the plain old auto-increment long integer as the primary key and use token as a unique key. That will prevent any unwanted duplicate entry.

The rest of the code looks okay.

omar jayed
  • 850
  • 5
  • 16
  • Yeah I don't have an `id` column in `users_token` table because initially I wanted a composite primary key from `token` and `user_id`. I think that I will follow your advice and just add the default `id` column. Thanks for your Help ;) – johannchopin Apr 09 '20 at 07:17
  • Glad to help... :) – omar jayed Apr 09 '20 at 07:20