In my Laravel project I'm having some trouble getting my hasManyThrough relationship to work, these are my models:
Pingtree
BuyerTier
PingtreeEntry
I want to get all of my BuyerTier models through my PingtreeEntry model.
This is my current relationship on my Pingtree model:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class Pingtree extends Model
{
use HasFactory, SoftDeletes;
/**
* The table associated with the model.
*
* #var string
*/
protected $table = 'pingtrees';
/**
* The attributes that should be cast.
*
* #var array<string, string>
*/
protected $casts = [
'is_enabled' => 'boolean',
];
/**
* The accessors to append to the model's array form.
*
* #var array
*/
protected $appends = [
'is_deleting',
];
/**
* Determine if we're editing the model
*
* #return bool
*/
public function getIsDeletingAttribute()
{
return false;
}
/**
* Get the company that owns the model.
*/
public function tiers()
{
return $this->hasManyThrough(
BuyerTier::class, // final model we want to access
PingtreeEntry::class, // intermediate model
'buyer_tier_id', // foreign key on intermediate model
'id', // foreign key on final model
'id', // local key
'pingtree_id' // local key on intermediate model
)->orderBy('processing_order', 'asc');
}
/**
* Get the pingtree entry model
*/
public function pingtree_entry()
{
return $this->belongsTo(PingtreeEntry::class, 'id', 'pingtree_id');
}
/**
* Get the company that owns the model.
*/
public function company()
{
return $this->belongsTo(Company::class);
}
/**
* Get the user that owns the model.
*/
public function user()
{
return $this->belongsTo(User::class);
}
}
And then I query this in my controller:
$pingtree = Pingtree::where('company_id', $company_id)
->where('id', $id)
->with([
'tiers.buyer',
'tiers.pingtree_entry'
])
->first();
This is what my pingtree_entries table looks like:
Right now, for some reason, despite having multiple tiers on my pingtree ID 3, I'm only ever getting 1 result back in my query, and I should be seeing all 4 tiers on my pingtree, what am I missing?
Related
In my Laravel 8 project I have a model called Campaign, my front-end though is build in Vue JS so needs to have some keys on a Campaign for contextual purposes, such as opening and closing a dropdown menu when looping over the elements, a database column isn't nessecery for this.
I'd like to add some default key/value pairs to my Campaign model, for example: dropdown_is_open and should have a default value of false.
I came across the default attributes for a model and tried adding this but cannot see my new key on the object, what am I missing?
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class Campaign extends Model
{
use HasFactory, SoftDeletes;
/**
* Indicates if the model's ID is auto-incrementing.
*
* #var bool
*/
public $incrementing = false;
/**
* The table associated with the model.
*
* #var string
*/
protected $table = 'campaigns';
/**
* The attributes that are mass assignable.
*
* #var array<int, string>
*/
protected $fillable = [
'campaign',
'template'
];
/**
* The model's default values for attributes.
*
* #var array
*/
protected $attributes = [
'dropdown_is_open' => false
];
}
Index function in controller:
/**
* Display a listing of the resource.
*
* #return \Illuminate\Http\Response
*/
public function index()
{
$campaigns = Campaign::where('user_id', Auth::id())
->orderBy('created_at', 'desc')
->get();
if (!$campaigns) {
return response()->json([
'message' => "You have no campaigns"
], 404);
}
return response()->json([
'campaigns' => $campaigns
], 200);
}
I expect to see:
{
campaign: 'my campaign',
template: '',
dropdown_is_open: false <-- my key
}
Previously I was doing a foreach in my index function and adding the contextual keys on each item, but this would only show for the index function and I'd have to add it everywhere.
I hope something like below helps.
Change it from my_custom_field to dropdown_is_open key (and from getMyCustomFieldAttribute to getDropdownIsOpenAttribute method-name).
Custom attribute (or Accessor)
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class User extends Model {
protected $appends = ['my_custom_field'];
public function getMyCustomFieldAttribute()
{
return false;
}
}
The $appends in above is required only,
to ensure that my_custom_field is preset/cached, and even sent as JSON-Response.
I would like to query pivot model relation using Eloquent.
I've my User model :
class User extends Authenticatable
{
public function preferences(): BelongsToMany
{
return $this->belongsToMany(Preference::class, 'user_preference')
->using(UserNotificationPreference::class) //Custom Pivot model
->withPivot([enabled, channel_id]);
}
}
Here is the custom pivot model :
class UserNotificationPreference extends Pivot
{
/**
* The attributes that should be casted to native types.
*
* #var array
*/
protected $casts = [
'enabled' => 'boolean'
];
/**
* Channel relation.
*
* #return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function channel(): BelongsTo
{
return $this->belongsTo(Channel::class);
}
}
And the preference model :
class Preference extends Model
{
// protected $connection = "apodis";
/**
* The users that belong to the preference.
*
* #return \Illuminate\Database\Eloquent\Relations\BelongsToMany
*/
public function users(): BelongsToMany
{
return $this->belongsToMany(Preference::class, 'user_channel_notification_preference')
->using(UserNotificationPreference::class) //custom pivot
->withPivot(['preference_id', 'user_id', 'enabled', 'channel_id']);
}
}
From a User model, i would like to retrieve Preferences after querying custom pivot table relationship (Channel::class) ,
something like :
$user->preferences()
->wherePivot('enabled', true)
->whereHasPivot('channel', function(Builder $query) {
//doesn't exists
})->get()
There is a way to achieve this ?
(Products Model Laravel) public function products() {
return $this->belongsToMany('App\Product');
} (Shop Model Laravel)public function shops(){ return $this->belongsToMany('App\Shop');} you can specify the actual field names of that pivot table public function products(){ return $this->belongsToMany('App\Product','products_shops', 'shops_id', 'products_id');}possibility to get those values in our loops foreach ($shop-products as $product){echo $product->pivot->price;}
I'm building a Laravel 8 API and want to automatically join user_settings onto a user whenever the User model is queried.
My thinking is that I can achieve this with the belongsTo relationship since user_settings "belongs" to a user.
However, when I attach this to my UserSetting model and query a user I'm not seeing any user settings attached to my User despite having data in the user_settings table.
Where am I going wrong?
Model: User
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class UserSetting extends Model
{
use HasFactory, SoftDeletes;
/**
* The table associated with the model.
*
* #var string
*/
protected $table = 'user_settings';
/**
* The attributes that are mass assignable.
*
* #var array
*/
protected $fillable = [
'user_id',
'theme',
'refreshButtonPlacement',
'animationSpeed',
'fetchTimeout'
];
/**
* Get the user that owns the comment.
*/
public function user()
{
return $this->belongsTo(UserSetting::class);
}
}
Model: User
<?php
namespace App\Models;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Factories\HasFactory;
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
{
use HasFactory, 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'
];
/**
* The attributes that should be cast to native types.
*
* #var array
*/
protected $casts = [
'email_verified_at' => 'datetime',
'last_login_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 [];
}
}
I also tried using a One To One relationship and defined a settings method on my User model but in Tinker when I ran User::findOrFail(1)->settings; I had nothing either.
Relationship setup:
class User extends Model
{
//some custom stuff
/**
* Get the phone associated with the user.
*/
public function user_setting()
{
return $this->hasOne(UserSetting::class);
}
}
class UserSetting extends Model
{
//some custom things
/**
* Get the user that owns the comment.
*/
public function user()
{
return $this->belongsTo(User::class);
}
}
Afterwards you can use eager laoding by default, in your case you will have to add $with = ['user_setting'] to your User class.
You could also use the ->with() method, for that you will have to use either:
User::with('user_setting')->find(Auth::id());
//or
Auth::user()->with('organisation')->first()
Laravel doesn't load the relationship values in every call because of the obvious overhead. So you will either define the relationship to be loaded by default or you will have to work with the ->with() method for eager loading the relationship.
Add this method to your User model
And you can access the user settings through a dynamic attribute $user-> user_setting
on each User model instance
For more informations
https://laravel.com/docs/8.x/eloquent-relationships#one-to-one
public function user_setting(){
return $this->hasOne(UserSetting::class);
}
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';
// ...
}