A little background. I am attempting to register a new user, create a default profile for them and then log them in. The users table as a current_profile_id column.
I am using UUID's for this, there is no incrementing ID's as all. My migrations appear to be all set up correctly, but something is going wrong when the relationships are being saved. At the end of the process my newly created profile has no ID to it.
UuidTrait.php:
<?php
namespace MyNamespace\Traits;
use Ramsey\Uuid\Uuid;
use Illuminate\Database\Eloquent\ModelNotFoundException;
/**
* Trait UuidTrait
* #package MyNamspace\Traits
*/
trait UuidTrait
{
/**
* Boot function from laravel.
*/
protected static function boot()
{
parent::boot();
static::creating(function ($model) {
$model->{$model->getKeyName()} = Uuid::uuid4()->toString();
});
}
}
User.php (Showing the relationships only)
/**
* Has many profiles
*
* #return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function profiles()
{
return $this->hasMany('MyNamespace\Models\Profile');
}
/**
* Current profile
*
* #return \Illuminate\Database\Eloquent\Relations\belongsTo
*/
public function currentProfile()
{
return $this->hasOne('MyNamespace\Models\Profile', 'id', 'current_profile_id');
}
AuthController.php (register() the creation of it all)
$user = $this->user->create($request->all());
$profile = $user->profiles()->create(['username' => $request->username]);
$user->currentProfile()->save($profile);
My user is being created with an ID but for some reason my profile is being created without. I have two suspicions. Either its my Trait as I am creating from a relationship or its the relationship, any ideas?
Related
I've read part of the Laravel docs for events and closures for models, I've got various models in my project whereby a user may have data linked to them in another table by a user_id column, the user_id column that I have in my various tables is structured as an unsigned integer (I'm aware I could've gone with a foreignId column by kind of a legacy approach here)
It looks like:
$table->integer('user_id')->unsigned()->nullable()->index();
I'd like to delete user data by their ID within these other tables and rather than creating a delete function and grabbing each model I want to delete data against, I've utilised the closure booted function and what I believe to be an event to listen and delete related model data, but I experience an error when trying to delete my user account, other data in other tables isn't deleted, the error I get is:
Call to undefined method App\Models\User::releationship()
My user model looks like:
<?php
namespace App\Models;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Database\Eloquent\Model;
use Tymon\JWTAuth\Contracts\JWTSubject;
class User extends Authenticatable implements JWTSubject, MustVerifyEmail
{
use Notifiable, SoftDeletes;
/**
* The attributes that are mass assignable.
*
* #var array
*/
protected $fillable = [
'first_name', 'last_name', 'email', 'password'
];
/**
* The attributes that should be hidden for arrays.
*
* #var array
*/
protected $hidden = [
'password', 'remember_token'
];
/**
* The attributes that should be cast to native types.
*
* #var array
*/
protected $casts = [
'email_verified_at' => 'datetime'
];
/**
* Get the identifier that will be stored in the subject claim of the JWT.
*
* #return mixed
*/
public function getJWTIdentifier()
{
return $this->getKey();
}
/**
* Return a key value array, containing any custom claims to be added to the JWT.
*
* #return array
*/
public function getJWTCustomClaims()
{
return [];
}
/**
* Route notifications for the Slack channel.
*
* #param \Illuminate\Notifications\Notification $notification
* #return string
*/
public function routeNotificationForSlack($notification)
{
$url = $this->slack_webhook;
$webhook = (isset($url) && !empty($url)) ? $url : null;
return $webhook;
}
/**
* The "booted" method of the model.
*
* #return void
*/
protected static function booted()
{
static::deleted(function ($model) {
$model->relationship()->delete();
});
}
}
And an example (of many) model I have, UptimeChecks looks like:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class UptimeChecks extends Model
{
/**
* The table associated with the model.
*
* #var string
*/
protected $table = 'uptime_checks';
/**
* Join user table
*/
public function user()
{
return $this->belongsTo('App\User');
}
}
All is then kicked off by a deleteAccount function in my API, which is deleting the user's account, but isn't deleting data in other tables. What am I missing and how could I do a check to make sure other data is deleted before confirming to the user that their account and linked data is gone?
/**
* Delete account
*
* #return Response
*/
public function deleteAccount(Request $request)
{
// attempt to delete account
try {
$user = User::findOrFail(Auth::id());
$user->delete();
// everything went okay!
return response()->json(['success' => true, 'message' => 'Your account has been deleted'], 200);
} catch (Exception $e) {
// catch the error
return response()->json(['success' => false, 'message' => 'We was unable to delete your account at this time'], 422);
}
}
In Laravel, when doing $model->relationship()->delete(); you will need to have the relationship defined and relationship() seems like it is copy pasted code snippet. Simply add the relationship to your User model.
class User extends Authenticatable implements JWTSubject, MustVerifyEmail
{
...
public function uptimeChecks() {
return $this->hasMany(UptimeChecks::class);
}
}
Now you can access and delete the relationship in your boot method.
$model->uptimeChecks()->delete();
You need to create a function in User.php
public function uptimeCheck()
{
return $this->hasOne('App\UptimeChecks');
}
and change the boot function
$model->uptimeCheck()->delete();
This way you need to do for all related relations.
This probably should be: $model->user()->delete() instead. There's nothing else.
If this shouldn't be the intention, reconsider the direction of the relationship.
I'm using https://github.com/spatie/laravel-permission
I have created a new class which extends the Role class. Here is the code for Role:
<?php
namespace Spatie\Permission\Models;
use Illuminate\Database\Eloquent\Model;
use Spatie\Permission\Traits\HasPermissions;
use Spatie\Permission\Exceptions\RoleDoesNotExist;
use Spatie\Permission\Contracts\Role as RoleContract;
use Spatie\Permission\Traits\RefreshesPermissionCache;
class Role extends Model implements RoleContract
{
use HasPermissions;
use RefreshesPermissionCache;
/**
* The attributes that aren't mass assignable.
*
* #var array
*/
public $guarded = ['id'];
/**
* Create a new Eloquent model instance.
*
* #param array $attributes
*/
public function __construct(array $attributes = [])
{
parent::__construct($attributes);
$this->setTable(config('laravel-permission.table_names.roles'));
}
/**
* A role may be given various permissions.
*
* #return \Illuminate\Database\Eloquent\Relations\BelongsToMany
*/
public function permissions()
{
return $this->belongsToMany(
config('laravel-permission.models.permission'),
config('laravel-permission.table_names.role_has_permissions')
);
}
/**
* A role may be assigned to various users.
*
* #return \Illuminate\Database\Eloquent\Relations\BelongsToMany
*/
public function users()
{
return $this->belongsToMany(
config('auth.model') ?: config('auth.providers.users.model'),
config('laravel-permission.table_names.user_has_roles')
);
}
/**
* Find a role by its name.
*
* #param string $name
*
* #throws RoleDoesNotExist
*
* #return Role
*/
public static function findByName($name)
{
$role = static::where('name', $name)->first();
if (! $role) {
throw new RoleDoesNotExist();
}
return $role;
}
/**
* Determine if the user may perform the given permission.
*
* #param string|Permission $permission
*
* #return bool
*/
public function hasPermissionTo($permission)
{
if (is_string($permission)) {
$permission = app(Permission::class)->findByName($permission);
}
return $this->permissions->contains('id', $permission->id);
}
}
My code was working fine when accessing this Role class directly for create()'s, but attempting to perform the same tasks using my new UserRole class, I am getting Column not found database errors when attempting to create a new Role.
Here is the UserRole class:
namespace App;
use Spatie\Activitylog\Traits\LogsActivity;
use Spatie\Permission\Models\Role;
class UserRole extends Role
{
use LogsActivity;
/**
* The attributes that should be logged.
*
* #var array
*/
protected static $logAttributes = ['name', 'permissions'];
}
So Role::create() works fine, but UserRole::create() does not.
Well changing the name to Role and then changing my use clause to as SpatieRole has fixed the issue. I'm guessing it was some type of class name relationship issue with Eloquent.
If you don't define the $table property on your Eloquent model, the table name is derived from the name of the Model. So, the Role model would use the roles table by default. The UserRole model would look for the user_roles table by default.
Since you still want to use the same table, but your model name is changed, you will need to define the $table property on your new model to make it look at the roles table.
class UserRole extends Role
{
protected $table = 'roles';
// ...
}
Okay, so I'm trying to implement an ACL using Laravel on an Intranet and I'm having some problems with permissions growing rapidly out of control. So first off, here's what I've got:
My five tables defining my users, my roles and my permissions like this:
tblIntranetUser
UserID
Name
FirstName
Username
tblIntranetRoles
RoleID
RoleName
Description
tblIntranetPermissions
PermissionID
PermissionName
Description
tblIntranetRoles_Permissions
RoleID
PermissionID
tblIntranetUsers_Roles
UserID
RoleID
And also I have the AuthServiceProvider as well as the Permission and Role models:
class Permission extends Model
{
/**
* The database table used by the model.
*
* #var string
*/
protected $table = 'tblIntranetPermissions';
protected $primaryKey = 'PermissionID';
public $timestamps = false;
/**
* The attributes that are mass assignable.
*
* #var array
*/
protected $fillable = ['PermissionID', 'PermissionName', 'Description'];
public function roles()
{
return $this->belongsToMany('App\Role', 'tblIntranetRoles_Permissions', 'PermissionID', 'RoleID');
}
public function detachAllRoles()
{
$roles = $this->roles;
foreach($roles as $role){
$role->permissions()->detach($this);
}
}
}
namespace App;
use Illuminate\Database\Eloquent\Model;
use App\User;
class Role extends Model
{
/**
* The database table used by the model.
*
* #var string
*/
protected $table = 'tblIntranetRoles';
protected $primaryKey = 'RoleID';
public $timestamps = false;
/**
* The attributes that are mass assignable.
*
* #var array
*/
protected $fillable = ['RoleID', 'RoleName', 'Description'];
public function permissions()
{
return $this->belongsToMany('App\Permission', 'tblIntranetRoles_Permissions', 'RoleID', 'PermissionID');
}
public function givePermissionTo(Permission $permission)
{
return $this->permissions()->save($permission);
}
public function getUsers()
{
$users = User::orderBy('UserID')->get();
$roleusers = collect();
foreach($users as $user){
if($user->hasRole($this->name)){
$roleusers->push($user);
}
}
return $roleusers;
}
public function detachAllUsers()
{
$users = $this->getUsers();
foreach($users as $user){
$user->roles()->detach($this);
}
}
public function detachAllPermissions()
{
$permissions = $this->permissions;
foreach($permissions as $permission){
$permission->roles()->detach($this);
}
}
}
namespace App\Providers;
use App\Report, App\Permission;
use Illuminate\Contracts\Auth\Access\Gate as GateContract;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
class AuthServiceProvider extends ServiceProvider
{
/**
* The policy mappings for the application.
*
* #var array
*/
protected $policies = [
];
/**
* Register any application authentication / authorization services.
*
* #param \Illuminate\Contracts\Auth\Access\Gate $gate
* #return void
*/
public function boot(GateContract $gate)
{
$this->registerPolicies($gate);
foreach ($this->getPermissions() as $permission){
$gate->before(function ($user) {
if ($user->isSuperAdmin()) {
return true;
}
});
$gate->define($permission->name, function($user) use ($permission){
return $user->hasRole($permission->roles);
});
}
}
protected function getPermissions()
{
return Permission::with('roles')->get();
}
}
So, thanks to this, I was able to create various roles and assign permissions to them which allows them to access certain sections of the Intranet as well as see certain reports. For example, I can define the following:
Role: Analyst
Access: Section 1, 2, 3
Reports: 1,15,41
Role: Developer
Access: All sections
Reports: All reports
It would be fine if every analyst could see and access the same sections... but of course that's not the case. Same goes for developers. Following this model, it basically means I need to have one role for every user as well as one permission for every possible element on the Intranet. Given that there's roughly 200 reports available as well as about 30 users, this makes for a lot of "show_report_1", "show_report_2", "show_section_1", "show_section_2" permissions (Laravel identifies permissions by name).
So, in order to make things a bit more... orderly I guess, I've been wondering if there wouldn't be a way to have one permission named "show_report" with the reportID stored in another field and to avoid having one role per user.
I'm not sure of the "proper" way to do this, but you could add an extra row to one of your pivot tables (probably the role_permission one) and use that to store more specific data about the permission. (eg. sections they can access)
Check out here for accessing pivot values: https://laravel.com/docs/5.5/eloquent-relationships#many-to-many
$role = App\Role::find(1);
foreach ($role->permissions as $permission) {
echo $permission->pivot->permission_settings; // [1,2,3]
}
That way you could have a single permission of "access_section", and then just check the pivot to see what sections they can access.
(There is probably a better or 'proper' way to do this though)
I'm new to Laravel 5 and I have some difficulties with pivot tables, controllers and repositories.
I have the tables 'users', 'sites', 'site_user', and here is what I have now :
App\Models\User
class User extends Model implements AuthenticatableContract, CanResetPasswordContract {
protected $table = 'users';
public function sites()
{
return $this->belongsToMany('App\Models\Site')
->withPivot('site_id', 'user_id', 'relation');
}
}
App\Models\Site
class Site extends Model {
protected $table = 'sites';
public function user()
{
return $this->belongsToMany('App\Models\User')
->withPivot('site_id', 'user_id', 'relation');
}
}
App\Repositories\SiteRepository
<?php namespace App\Repositories;
use App\Models\Site, App\Models\User;
class SiteRepository extends BaseRepository
{
/**
* The User instance.
*
* #var App\Models\User
*/
protected $user;
/**
* Create a new SiteRepository instance.
*
* #param App\Models\Site $site
* #return void
*/
public function __construct (Site $sites, User $user)
{
$this->model = $sites;
$this->user = $user;
}
/**
* Get sites collection paginate.
*
* #param int $n
* #return Illuminate\Support\Collection
*/
public function index($n)
{
return $this->model
->latest()
->paginate($n);
}
App\Http\Controllers\SiteController
<?php namespace App\Http\Controllers;
use App\Repositories\SiteRepository;
use App\Repositories\UserRepository;
use App\Http\Requests\SiteCreateRequest;
use App\Http\Requests\SiteUpdateRequest;
use App\Models\Site;
use App\Models\User;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
class SiteController extends Controller {
/**
* The SiteRepository instance.
*
* #var App\Repositories\SiteRepository
*/
protected $site_gestion;
/**
* The UserRepository instance.
*
* #var App\Repositories\UserRepository
*/
protected $user_gestion;
/**
* Create a new SiteController instance.
*
* #param App\Repositories\SiteRepository $site_gestion
* #param App\Repositories\UserRepository $user_gestion
* #return void
*/
public function __construct (SiteRepository $site_gestion, UserRepository $user_gestion)
{
$this->site_gestion = $site_gestion;
$this->user_gestion = $user_gestion;
$this->middleware('admin');
}
/**
* Display a listing of the resource.
*
* #return Response
*/
public function index(SiteRepository $site_gestion)
{
//$counts = $this->site_gestion->counts();
$sites = $site_gestion->index(25);
$links = $sites->render();
return view('back.sites.index', compact('sites'));
}
views\back\sites\table.blade.php
#foreach ($sites as $site)
[...some code...]
#endforeach
What I want to do is to get all the sites of the logged in user. I've tried many things, but none of them are working. And I'm still not sure where to put the code, repository or controller...
I've read tutorials about pivot in Laravel, and I've tried with some things like this in the repo, but it doesn't work...
$user = $this->user->find(auth()->user()->id); //This line is working
foreach ($user->sites as $site) {
return $site
->latest()
->paginate($n);
}
If you want all sites of a logged user simply do it like this:
$sites = Auth::user()->sites;
That's all you need to do to get to these sites. If you want to use query and pagination try like this:
$sites = Auth::user()->sites()->latest()->paginate($n);
So what you've done seems pretty close.
So you pretty much have it, when iterating over the sites they should be instances of the site model.
$user = auth()->user(); // This is a way of saying your first line without a db query for the user
foreach ($user->sites as $site) {
// each site in here is a site model
$site->pivot->relation;
}
The only other thing that looks slightly strange is how you've defined the pivots. Generally when calling withPivot you wouldn't define the joining ids, if you wish to vary from the defaults you can pass it as an argument to the belongsToMany like so.
return $this->belongsToMany('App\Models\User', 'site_user', 'user_id', 'site_id')
->withPivot('relation');
I just have a question about add the three "RememberToken" public functions (getRememberToken(), setRememberToken(), and getRememberTokenName() ). For some reason if I tried to log in and create a new session my page would crash and I would get the "Class Foo contains 3 abstract methods..." until I had add all three to every model I had. The weird thing is that I was trying to sign in with Sessions class but I would get this error until I added the new RememberToken functions to every class I have. Is this normal? Do I need to add the "remember_token" to every table that I use now? If anyone could explain why this is or how I went wrong that would be greatly appreciated! Thanks so much!
Here is an example of one of my models with the 3 RememberToken functions:
<?php
use Illuminate\Auth\UserInterface;
use Illuminate\Auth\Reminders\RemindableInterface;
class Warranty extends Eloquent implements UserInterface, RemindableInterface {
protected $fillable = array( 'id', 'created_by', 'street_address', 'warranty_serialized');
/**
* The database table used by the model.
*
* #var string
*/
protected $table = 'warranties';
/**
* The attributes excluded from the model's JSON form.
*
* #var array
*/
protected $hidden = array('password');
/**
* Get the unique identifier for the order.
*
* #return mixed
*/
public function getAuthIdentifier()
{
return $this->getKey();
}
/**
* Get the password for the order.
*
* #return string
*/
public function getAuthPassword()
{
return $this->password;
}
/**
* Get the e-mail address where password reminders are sent.
*
* #return string
*/
public function getReminderEmail()
{
return $this->email;
}
/*4.26 Update to RememberToken*/
public function getRememberToken()
{
return $this->remember_token;
}
public function setRememberToken($value)
{
$this->remember_token = $value;
}
public function getRememberTokenName()
{
return 'remember_token';
}
}
You have to add it to the all models that
implements UserInterface, RemindableInterface
Because those are basically User tables.