Laravel define a default key with value on Model - php

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.

Related

Trouble getting hasManyThrough to work in Laravel 9

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?

Laravel 8 belongsTo relationship not returning data on User model

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);
}

Laravel delete linked model's data by user_id in booted function of User model

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.

Laravel - makeVisible doesn't make hidden attribute visible

I have the following code:
$model = new coretable;
log::info($model->all());
$model = $model->makeVisible('id_coretable');
log::info($model->all());
In my lumen log, I get the following result:
[2020-02-26 10:14:19] local.INFO: [{"Internal_key":"TESTKEY_1"},{"Internal_key":"TESTKEY_2"},{"Internal_key":"TESTKEY_3"},{"Internal_key":"TESTKEY_4"},{"Internal_key":"TESTKEY_5"}]
[2020-02-26 10:14:19] local.INFO: [{"Internal_key":"TESTKEY_1"},{"Internal_key":"TESTKEY_2"},{"Internal_key":"TESTKEY_3"},{"Internal_key":"TESTKEY_4"},{"Internal_key":"TESTKEY_5"}]
I would expect the "id_coretable" attribute to be present in the second output from log::info(), but it isnt.
Why is that?
Here is the model of coretable:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class CoreTable extends Model
{
/**
* The table associated with the model.
*
* #var string
*/
protected $table = 'coretable';
/**
* The attributes that are mass assignable.
*
* #var array
*/
protected $fillable = [
'Internal_key'
];
protected $hidden = [
'id_coretable',
'created_at',
'updated_at'
];
protected $primaryKey = 'id_coretable';
/**
* Many-To-Many relationship with User-Model.
*/
public function extensiontable_itc()
{
return $this->hasOne('App\extensiontable_itc', 'coretable_id');
}
public function extensiontable_sysops()
{
return $this->hasOne('App\extensiontable_sysops', 'coretable_id');
}
public function inaccessibletable()
{
return $this->hasOne('App\inaccessibletable', 'coretable_id');
}
}
I have no clue why makeVisible() doesnt have any effect on the effect.
The initial model you created does not have any influence on the models received from the all() function. This is a collection of new models with the initial $hidden array.
To change what values are shown, you will have to call makeVisible on the collection you receive:
$model = new coretable;
log::info($model->all());
log::info($model->all()->makeVisible('id_coretable'));
It is also recommended to call the query functions staticaly, this way you don't need to create an initial model:
log::info(coretable::all()->makeVisible('id_coretable'));

Laravel and Eloquent saving relationship on the hasOne side

Ok, working in Laravel with eloquent so i have the concept of a ContentType model and a Template model. I have a form where you set data for the Content Type and you select from a drop down list of templates which one is to be associated with the content type. My models look like this:
ContentType:
namespace App;
use Illuminate\Database\Eloquent\Model;
use \Hyn\Tenancy\Traits\UsesTenantConnection;
class ContentType extends Model
{
use UsesTenantConnection;
/**
* The attributes that are mass assignable.
*
* #var array
*/
protected $fillable = [
'parent_content_id', 'name', 'description', 'is_requestable', 'status',
];
/**
* Get the template that owns the content type.
*/
public function template()
{
return $this->belongsTo('App\Template');
}
/**
* Get the entity groups for the content type.
*/
public function entity_groups()
{
return $this->hasMany('App\EntityGroup');
}
/**
* Get the entities for the content type.
*/
public function entities()
{
return $this->hasMany('App\Entity');
}
}
Template:
namespace App;
use Illuminate\Database\Eloquent\Model;
use \Hyn\Tenancy\Traits\UsesTenantConnection;
class Template extends Model
{
use UsesTenantConnection;
/**
* The attributes that are mass assignable.
*
* #var array
*/
protected $fillable = [
'name', 'filename', 'status',
];
/**
* Get the content types for the template.
*/
public function content_types()
{
return $this->hasMany('App\ContentType');
}
}
What i want to do is store or update the template value. Is there a way of doing this straight off of . the ContentType model instead of saving that relationship through the Template model? Or should i adjust the types of relationship i have in the first place?
You could do it like this:
$contentType->template()->update([
'colToBeUpdated' => 'new value',
]);
This will execute 1 query to update the template of the $contentType.
But if you just want to change the template_id (to associate $contentType with some other template) you could just do:
$contentType->template()->associate($newId);
Or
$contentType->update(['template_id' => $newId]);

Categories