Ali
Ali

Reputation: 357

I got this error Call to undefined method App\\Models\\User::assignRole()

I have a small app & I'm using Laravel 11 and I try to expose only the UILD and not IDs and I got "Call to undefined method App\Models\User::assignRole()"

User.php

    <?php

namespace App\Models;

use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Sanctum\HasApiTokens;
use Spatie\Permission\Traits\HasRoles; // Ensure this is imported
use Illuminate\Support\Str;
use Spatie\Permission\Models\Role;
use Illuminate\Support\Facades\DB;

class User extends Authenticatable implements MustVerifyEmail
{
    use HasApiTokens, HasFactory, Notifiable, HasRoles;


    /**
     * The attributes that are mass assignable.
     */
    protected $fillable = [
        'name',
        'email',
        'password',
        'ulid',
        'store_id',
        'is_active',
        'wpuserId',
        'phone',
        'avatar_url',
        'description',
    ];

    /**
     * The attributes that should be hidden for serialization.
     */
    protected $hidden = [
        'id',
        'ulid',
        'password',
        'remember_token',
        'email_verified_at',
    ];

    /**
     * The attributes that should be cast to native types.
     */
    protected $casts = [
        'email_verified_at' => 'datetime',
        'password' => 'hashed',
        'is_active' => 'boolean',
    ];

    /**
     * Boot the model.
     */
    protected static function boot()
    {
        parent::boot();

        static::creating(function ($user) {
            if (empty($user->ulid)) {
                $user->ulid = Str::ulid()->toBase32(); // Automatically generate a ULID
            }
        });
    }

    /**
     * Relationship with UserProvider.
     */
    public function userProviders(): HasMany
    {
        return $this->hasMany(UserProvider::class);
    }

    /**
     * Relationship with Store.
     */
    public function store(): BelongsTo
    {
        return $this->belongsTo(Store::class);
    }

    /**
     * Assign roles to the user and update `model_has_roles` with ULID.
     */
    public function assignRole(...$roles): static
    {
        \Log::info('User.php > Inside assignRole method', ['roles' => $roles, 'user_id' => $this->id]);
        // Log the roles being assigned
        \Log::info('User.php > Assigning roles to user', [
            'user_ulid' => $this->ulid,
            'user_id' => $this->id,
            'roles' => $roles,
        ]);

        try {
            // Call the Spatie method to assign roles
            \Log::info('User.php > Inside try roles', $roles);
            parent::assignRole(...$roles);

            // Log after the parent method call
            \Log::info('User.php > Parent assignRole method executed', [
                'user_id' => $this->id,
            ]);

            // Update the ULID in the model_has_roles table for each role
            foreach ($roles as $role) {
                \Log::info('User.php > Processing role', [
                    'role_name' => $role,
                ]);

                $roleModel = Role::where('name', $role)->first();

                if ($roleModel) {
                    \Log::info('User.php > Role found', [
                        'role_id' => $roleModel->id,
                    ]);

                    $updatedRows = DB::table('model_has_roles')
                        ->where('role_id', $roleModel->id)
                        ->where('model_id', $this->id)
                        ->update([
                            'model_ulid' => $this->ulid,
                            'updated_at' => now(),
                        ]);

                    \Log::info('User.php > model_has_roles table updated', [
                        'role_id' => $roleModel->id,
                        'model_id' => $this->id,
                        'updated_rows' => $updatedRows,
                    ]);
                } else {
                    \Log::warning('User.php > Role not found', [
                        'role_name' => $role,
                    ]);
                }
            }

            return $this;
        } catch (\Exception $e) {
            // Log the exception if something goes wrong
            \Log::error('User.php > Error assigning roles', [
                'error_message' => $e->getMessage(),
                'user_id' => $this->id,
            ]);

            throw $e; // Optionally re-throw the exception
        }
    }


    /**
     * Relationship with roles.
     */
    public function roles(): BelongsToMany
    {
        return $this->belongsToMany(Role::class, 'model_has_roles', 'model_id', 'role_id')
            ->wherePivot('model_type', self::class)
            ->withPivot('model_ulid'); // Include ULID in the relationship
    }

    /**
     * Activate the user.
     */
    public function activate(): void
    {
        $this->is_active = true;
        $this->save();
    }

    /**
     * Deactivate the user.
     */
    public function deactivate(): void
    {
        $this->is_active = false;
        $this->save();
    }
}

Role.php

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Spatie\Permission\Models\Role as SpatieRole;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use App\Models\User;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;

class Role extends SpatieRole
{
    use HasFactory;

    /**
     * The attributes that are mass assignable.
     *
     * @var array<int, string>
     */
    protected $fillable = [
        'name',
        'guard_name',
        'ulid', // Add ULID to fillable fields
    ];

    /**
     * The attributes that should be cast to native types.
     *
     * @var array<string, string>
     */
    protected $casts = [
        'created_at' => 'datetime',
        'updated_at' => 'datetime',
    ];

    /**
     * Automatically generate ULID for roles on creation.
     */
    protected static function boot()
    {
        parent::boot();

        static::creating(function ($role) {
            if (empty($role->ulid)) {
                $role->ulid = Str::ulid()->toBase32(); // Generate ULID automatically
            }
        });
    }

    /**
     * Assign this role to a user.
     */
    public function assignToUser(User $user): void
    {
        DB::table('model_has_roles')->insert([
            'role_id' => $this->id,
            'model_id' => $user->id,
            'model_type' => User::class,
            'model_ulid' => $user->ulid, // Add user ULID during insertion
            'created_at' => now(),
            'updated_at' => now(),
        ]);
    }

    /**
     * The users that belong to the role.
     */
    public function users(): BelongsToMany
    {
        return $this->belongsToMany(User::class, 'model_has_roles', 'role_id', 'model_id')
            ->withPivot('model_ulid', 'model_type'); // Include ULID and model type in pivot
    }
}

Permission.php

    <?php
    
    
    
    namespace App\Models;
    
    use Illuminate\Database\Eloquent\Factories\HasFactory;
    use Spatie\Permission\Models\Permission as SpatiePermission;
    use App\Models\Permission as CustomPermission;
    use Illuminate\Database\Eloquent\Relations\BelongsToMany;
    use App\Models\Role;
    
    class Permission extends SpatiePermission
    {
        // Adding HasFactory trait for the Permission model
        use HasFactory;
    
        /**
         * The attributes that are mass assignable.
         *
         * @var array<int, string>
         */
        protected $fillable = [
            'name',
            'guard_name',
        ];
    
        /**
         * The attributes that should be cast to native types.
         *
         * @var array<string, string>
         */
        protected $casts = [
            'created_at' => 'datetime',
            'updated_at' => 'datetime',
        ];
        /**
         * The roles that belong to the permission.
         */
        public function roles(): BelongsToMany
        {
            return $this->belongsToMany(Role::class);
        }
    }

Permission.php

<?php

return [

    'models' => [

        'permission' => App\Models\Permission::class,
        'role' => Spatie\Permission\Models\Role::class,
    ],

    'table_names' => [

        'roles' => 'roles',
        'permissions' => 'permissions',
        'model_has_permissions' => 'model_has_permissions',
        'model_has_roles' => 'model_has_roles',
        'role_has_permissions' => 'role_has_permissions',
    ],

    'column_names' => [
        'role_pivot_key' => null, //default 'role_id',
        'permission_pivot_key' => null, //default 'permission_id',
        'model_morph_key' => 'model_id',
        'team_foreign_key' => 'team_id',
    ],
    'register_permission_check_method' => true,
    'register_octane_reset_listener' => false,
    'teams' => false,
    'use_passport_client_credentials' => false,
    'display_permission_in_exception' => false,
    'display_role_in_exception' => false,
    'enable_wildcard_permission' => false,

    'cache' => [

        'expiration_time' => \DateInterval::createFromDateString('24 hours'),
        'key' => 'spatie.permission.cache',
        'store' => 'default',
    ],
];

AuthController.php

<?php

namespace App\Http\Controllers;

use App\Models\User;
use App\Models\UserProvider;
use Illuminate\Auth\Events\PasswordReset;
use Illuminate\Auth\Events\Registered;
use Illuminate\Auth\Events\Verified;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Crypt;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Password;
use Illuminate\Support\Str;
use Illuminate\Validation\Rules;
use Illuminate\Validation\ValidationException;
use Laravel\Socialite\Facades\Socialite;
use Illuminate\View\View;
use App\Models\Store; // Import the Store model
use Illuminate\Support\Facades\Cache;
use Spatie\Permission\Models\Role;
use Illuminate\Support\Facades\DB;


class AuthController extends Controller
{
    /**
     * Register a new user.
     * When registering a new user, if the role is Admin or User, you might need to assign them to a store.
     * You could add a store_id to the request, and assign that store_id to the new use
     */
    public function register(Request $request): JsonResponse
    {
        \Log::info('AuthController > Start of register method');

        // Validate request data with required fields
        $validatedData = $request->validate([
            'name' => ['required', 'string', 'max:255'],
            'email' => ['required', 'string', 'email', 'max:255', 'unique:users,email'],
            'password' => ['required', 'confirmed', Rules\Password::defaults()],
            'role' => ['required', 'in:SuperAdmin,Admin,User'],
            'store_code' => ['nullable', 'exists:stores,store_code'],
            'phone' => ['nullable', 'string', 'max:15'],
            'avatar_url' => ['nullable', 'string', 'max:255'],
            'description' => ['nullable', 'string'],
            'wpuserId' => ['nullable', 'string'],
        ]);

        \Log::info('AuthController > Request data validated', ['validatedData' => $validatedData]);

        // Determine the role of the user
        $roleName = $validatedData['role'];

        // Check if the role exists
        $roleExists = Role::where('name', $roleName)->exists();
        \Log::info('AuthController > Role existence check', ['role' => $roleName, 'exists' => $roleExists]);

        if (!$roleExists) {
            \Log::error('AuthController > Role does not exist', ['role' => $roleName]);
            return response()->json(['error' => 'The specified role does not exist.'], 422);
        }

        // Validate store_code based on role
        if (in_array($roleName, ['Admin', 'User']) && empty($validatedData['store_code'])) {
            \Log::error('AuthController > Store code is missing for role', ['role' => $roleName]);
            return response()->json(['error' => 'store_code is required for Admin and User roles.'], 422);
        }

        // Get store_id based on store_code if provided
        $store_id = null;
        if (!empty($validatedData['store_code'])) {
            $store = Store::where('store_code', $validatedData['store_code'])->first();
            if (!$store) {
                \Log::error('AuthController > Invalid store code provided', ['store_code' => $validatedData['store_code']]);
                return response()->json(['error' => 'Invalid store code provided.'], 422);
            }
            $store_id = $store->id;
            \Log::info('AuthController > Store resolved', ['store_code' => $validatedData['store_code'], 'store_id' => $store_id]);
        }

        // Wrap user creation and role assignment in a database transaction
        try {
            DB::beginTransaction();
            \Log::info('AuthController > Beginning user registration transaction');

            // Create the user
            $user = User::create([
                'name' => $validatedData['name'],
                'email' => $validatedData['email'],
                'password' => Hash::make($validatedData['password']),
                'store_id' => $store_id,
                'phone' => $validatedData['phone'] ?? null,
                'avatar_url' => $validatedData['avatar_url'] ?? null,
                'description' => $validatedData['description'] ?? null,
                'wpuserId' => $validatedData['wpuserId'] ?? null,
            ]);

            \Log::info('AuthController > User created successfully', ['user_id' => $user->id]);

            // Log the traits used by the User model
            \Log::info('AuthController > User traits', ['traits' => class_uses($user)]);

            // Assign the role to the user
            $user->assignRole($roleName);
            \Log::info('AuthController > Role assigned to user', ['user_id' => $user->id, 'role' => $roleName]);

            // Commit the transaction
            DB::commit();
            \Log::info('AuthController > Transaction committed successfully');

            // Fire the Registered event
            event(new Registered($user));
            \Log::info('AuthController > Registered event fired', ['user_id' => $user->id]);

            return response()->json(['message' => 'User registered successfully'], 201);
        } catch (\Exception $e) {
            // Rollback the transaction on failure
            DB::rollBack();
            \Log::error('AuthController > Failed to register user', ['error' => $e->getMessage()]);

            // Return an error response
            return response()->json([
                'error' => 'AuthController > Failed to register user: ' . $e->getMessage(),
            ], 500);
        }
    }

the Log file:

[2024-11-18 01:43:06] local.INFO: AuthController > Start of register method [2024-11-18 01:43:06] local.INFO: AuthController > Request data validated {"validatedData":{"name":"John Doe","email":"[email protected]","password":"password123","role":"Admin","store_code":"3456789","phone":"+1234567890","avatar_url":"https://example.com/avatar.jpg","description":"This is a description.","serId":"wp_12345"}} [2024-11-18 01:43:06] local.INFO: AuthController > Role existence check {"role":"Admin","exists":true} [2024-11-18 01:43:06] local.INFO: AuthController > Store resolved {"store_code":"3456789","store_id":1} [2024-11-18 01:43:06] local.INFO: AuthController > Beginning user registration transaction [2024-11-18 01:43:07] local.INFO: AuthController > User created successfully {"user_id":13} [2024-11-18 01:43:07] local.INFO: AuthController > User traits {"traits":{"Laravel\Sanctum\HasApiTokens":"Laravel\Sanctum\HasApiTokens","Illuminate\Database\Eloquent\Factories\HasFactory":"Illuminate\Database\Eloquent\Factories\HasFactory","Illuminate\Notifications\Notifiable":"Illuminate\Notifications\Notifiable","Spatie\Permission\Traits\HasRoles":"Spatie\Permission\Traits\HasRoles"}} [2024-11-18 01:43:07] local.INFO: User.php > Inside assignRole method {"roles":["Admin"],"user_id":13} [2024-11-18 01:43:07] local.INFO: User.php > Assigning roles to user {"user_ulid":"01JCYF054BBP8EQCT4ZN2J","user_id":13,"roles":["Admin"]} [2024-11-18 01:43:07] local.INFO: User.php > Inside try roles ["Admin"] [2024-11-18 01:43:07] local.ERROR: User.php > Error assigning roles {"error_message":"Call to undefined method App\Models\User::assignRole()","user_id":13} [2024-11-18 01:43:07] local.ERROR: AuthController > Failed to register user {"error":"Call to undefined method App\Models\User::assignRole()"}

Upvotes: 0

Views: 91

Answers (1)

theihasan
theihasan

Reputation: 9

First, modify the User model to properly implement Illuminate\Database\Eloquent\Concerns\HasUlids

Now in your user model

public function uniqueIds(): array
{
    return ['ulid'];
}

make sure in your migration $table->ulid('id');

then

php artisan optimize:clear

Upvotes: -1

Related Questions