Not passing id into model in Laravel - php

I am using Eloquent's firstOrCreate() method to insert my data, or retrieve if it exists.
I need to get the $trip->id. If I echo it in the FlightController then it echo's the correct id, but it doesn't seem to pass it into the UserFlights Model to insert into the user_flights table.
I get this error on the page:
QueryException in Connection.php line 729: SQLSTATE[23000]: Integrity
constraint violation: 1048 Column 'user_trips_id' cannot be null (SQL:
insert into user_flights (airport_from, airport_to,
user_trips_id) values (1, 4, ))
Flow: User select's two airports (flying from and flying to) from the drop-down boxes (<select>) and adds them to their "trip". If they don't have a trip, it creates one, and then adds the two airports to their trip.
Schema
# `user_trips` table
Schema::create('user_trips', function (Blueprint $table) {
$table->increments('id');
$table->integer('user_id')->unsigned()->index();
$table->text('name');
});
# `user_flights ` table
Schema::create('user_flights', function (Blueprint $table) {
$table->increments('id');
$table->integer('trip_id')->unsigned();
$table->integer('airport_from')->unsigned();
$table->integer('airport_to')->unsigned();
$table->foreign('trip_id')->references('id')->on('user_trips')->onDelete('cascade');
});
FlightController
<?php
namespace App\Http\Controllers;
use App\UserFlights;
use App\UserTrips;
use Illuminate\Http\Request;
/**
* Class FlightController
*
* #package App\Http\Controllers
*/
class FlightController extends Controller
{
/**
* #param Request $request
* #param UserTrips $user_trips_obj
* #return \Illuminate\Http\RedirectResponse
*/
public function store(Request $request, UserTrips $user_trips_obj)
{
// Retrieve the trip by the attributes, or create it if it doesn't exist...
$trip=$user_trips_obj->addTrip();
# Returns the ID correctly.
//echo $trip->id;exit;
$user_trips_obj->addFlight(
new UserFlights([
'airport_from'=>$request->flight_from,
'airport_to'=>$request->flight_to,
# Does not pass the `$trip->id` into the UserFlights model.
'user_trips_id'=>$trip->id
])
);
return back();
}
}
UserTrips Model
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
/**
* Class UserTrips
*
*/
class UserTrips extends Model
{
/**
* Indicates if the model should be timestamped.
*
* #var bool
*/
public $timestamps=FALSE;
/**
* The attributes that are mass assignable.
*
* #var array
*/
protected $fillable=[
'name',
'user_id'
];
/**
* #param UserFlights $user_flights_obj
* #return Model
*/
public function addFlight(UserFlights $user_flights_obj)
{
return $this->userflights()->save($user_flights_obj);
}
/**
* #return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function userflights()
{
return $this->hasMany('App\UserFlights');
}
/**
* #return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function user()
{
return $this->belongsTo('App\User');
}
/**
* #return mixed
*/
public function addTrip()
{
// Retrieve the trip by the attributes, or create it if it doesn't exist...
$trip=$this->firstOrCreate([
'user_id'=>1,
'name'=>'My Trip'
]);
return $trip;
}
}
UserFlights Model
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
/**
* Class UserFlights
*
*/
class UserFlights extends Model
{
/**
* Indicates if the model should be timestamped.
*
* #var bool
*/
public $timestamps=FALSE;
/**
* The attributes that are mass assignable.
*
* #var array
*/
protected $fillable=[
'airport_from',
'airport_to',
'user_trips_id'
];
/**
* #return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function usertrips()
{
return $this->belongsTo('App\UserTrips');
}
}

Your addFlight() method calls $this->userflights()->save($user_flights_obj);.
When you call save() on the relationship object, it sets the foreign key on the object passed in ($user_flights_obj) to the id of the object that owns the relationship ($this). Then it saves the $user_flights_obj object.
Since your controller calls $user_trips_obj->addFlight(new UserFlights...), the $this reference inside your addFlight() method is referencing the $user_trips_obj instance from your controller. This instance is just an empty shell, with a blank id. Therefore, inside your addFlight() method, when you call save() on the relationship, it is going to set the foreign key on your new UserFlights instance to the id of the instance on which addFlight() was called (blank).
To solve this issue, you just need to call addFlight() on the $trip instance you created in the controller. That is the instance to which you want to relate the new UserFlights instance. Additionally, you don't need to set the foreign key manually; that is the whole reason for calling save() on the relationship.

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 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 eloquent relationship not accessing second table

I have 2 tables:
emails: email_id, name
email_templates: template_id, template_mid, template_lang, template_subject, template_mail
Template_mid is foreign key and associated with emails.id
My models:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Builder;
class Email extends Model
{
/**
* Indicates if the model should be timestamped.
*
* #var bool
*/
public $timestamps = false;
/**
* Indicates primary key column.
*
* #var bool
*/
protected $primaryKey = "email_id";
public function template()
{
return $this->hasOne('App\Email_template', 'template_mid', 'email_id');
}
}
Email_template
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Email_template extends Model
{
/**
* Indicates if the model should be timestamped.
*
* #var bool
*/
public $timestamps = false;
/**
* Indicates primary key column.
*
* #var bool
*/
protected $primaryKey = "template_id";
}
When I run this in my controller:
public function index()
{
$emails = Email::all();
dd($emails);
}
I cannot access the the template method and I have only id, subject in the dumped results. How can I fix this?
Related models are not loaded automatically. You can either load them with with(), or individually:
Email::with('template')->get();
or
foreach($emails as $email)
{
dd($email->template);
}

Extending model class causes Column not found error

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';
// ...
}

Data being saved with no ID's?

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?

Categories