laravel hasOneThrough with trashed does not remove the softdelete scope of intermediate - php

How to get all the relational records of timeslots when intermediate(hasOneThrough) has soft deletes.
1. Attendance Model Attendance.php
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Attendance extends Model
{
protected $fillable = [
'tutor_id',
'center_timeslot_id',
// other attributes are ommited
];
public function timeslot()
{
return $this->hasOneThrough(
Timeslot::class,
CenterTimeslot::class,
'id',
'id', // primary key on timeslots table...
'center_timeslot_id', //local key of this table to primary of centerTimeslots table
'timeslot_id' //local key on centertimeslots table of target table timeslot
)
->withTrashed();//this does not work
}
}
2. CenterTimeslot Model CenterTimeslot.php
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class CenterTimeslot extends Model
{
use SoftDeletes;
protected $fillable = [
'center_id', 'timeslot_id', 'capacity'
// other attributes are ommited
];
public function timeslot()
{
return $this->belongsTo(Timeslot::class);
}
}
3. Finally Timeslot Model Timeslot.php
<?php
namespace App\Models;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Model;
class Timeslot extends Model
{
protected $fillable = [
'name',
// other attributes are ommited
];
}
So if any record is soft deleted that is in center_timeslot_table we can not retrieve the model though applying withTrashed() as it only works on direct model on in the through table. So the soft deletes gets still applied as defined in CenterTimeslot Model
So there is no way to get the records.
But I can trick the query builder to have the method like this which does retrieve the records
Attendance.php
public function timeslot()
{
return $this->hasOneThrough(
Timeslot::class,
CenterTimeslot::class,
'id',
'id', // primary key on timeslots table...
'center_timeslot_id', //local key of this table to primary of centerTimeslots table
'timeslot_id' //local key on centertimeslots table of target table timeslot
)
->withTrashed()
->orWhere(function ($query) {
$query->whereNotNull('center_timeslots.deleted_at');
});
Sot it returns the record but I am not satisfied with the hack. So if someone can give some light or a better workaround will be good.

I face the same issue when my project was in the middle of development and my conclusion after searching on this is:
The documentation says at first coming note that:
And you make your pivot table a model therefore SoftDeletes are working.And withTrashed() is working for Timeslot::class but not for pivot table (CenterTimeslot) because laravel was not expecting a model here.
According to documentation, your Central or pivot table line should be
use Illuminate\Database\Eloquent\Relations\Pivot;
class CenterTimeslot extends Pivot
instead of
class CenterTimeslot extends Model
in this way, there will be the issue of SoftDeletes
In my case, I solve this by creating to classes one is extending from Pivot (which I am using for relation calls) and the other from Model (using where SoftDeletes or as a model is required) but I am not sure that this is the proper way or not but is working fine. I Will appreciate it if someone adds a proper way to deal with it but for the current time, it is working as required.
Note: Make the pivot class name different from the model and if the table name is different then you can define $table separately in pivot class.

Related

Getting wrong data from database even if I am fetching something different in laravel api

Hello guys I am working on a laravel project for making api for passing the database value in json format but the problem is I have a users table in this table 2 ids 1 is primary key and second is business _id I want to get data according to business_id but it's getting data by id please help me how to solve this issue.
Here is my model code
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class user extends Model
{
protected $table = 'business';
}
Here is my Controller Code
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\user;
class GetController extends Controller
{
public function databybusinessid($business _id){
$users = new user();
$users = user::find($business _id);
return response()->json($users);
}
}
Thank you so much
You are using user::find($business _id)
find() method will automatically search by the primary key but none is defined in your model and Eloquent can't decide which one to pick from your table. Therefore, you should explicitly set your primary key in your model by adding the following line.
class user extends Model
{
protected $table = 'business';
protected $primaryKey = 'business_id';
}
If in doubt, you can also fetch database record by a specific column using where
$users = user::where('business_id', '=', $business _id)->get()
Laravel documentation about Eloquent ORM
https://laravel.com/docs/5.8/eloquent
find() Retrieve a model by its primary key..
So you have to use your code as:
$users = user::where('business_id',$business_id)->first();
// Notice first() Retrieve the first model matching the query constraints...
Or you can change your primary code in model
namespace App;
use Illuminate\Database\Eloquent\Model;
class user extends Model
{
protected $table = 'business';
protected $primaryKey = 'business_id';
}
find() works only on primary key. you need to use where instead.
or you can define business_id as primary key in your User model.
protected $primaryKey = 'business_id';
public function databybusinessid($business _id){
$users = new user();
$users = user::where('business_id',$business _id)->first();
return response()->json($users);
}

How to Inverse the Eloquent Has One and Has Many Through (laravel 5.8)?

I have three relational table attached below.
https://drive.google.com/file/d/1q1kdURIwFXxHb2MgdRyBkE1e3DMug7r-/view?usp=sharing
I have also three separate models where defined relation among all of my table's.I can read the City Model's information from Country model using hasManyThrough() relation But cannot read the Country information from City model. I have tried to retrieve City model's using ``hasManyThrough``` but didn't get result (attached as commented country method ). Please read my model and it's relational method here..
Is there someone to help me for getting City model's information using Eloquent method hasManyThrough / hasManyThrough or using inverse of hasManyThrough / hasManyThrough ?
01.
<?php
namespace App\Hrm;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class Country extends Model
{
//use SoftDeletes;
protected $fillable = ['name','description','status'];
public function districts(){
return $this->hasMany(District::class);
}
public function cities(){
return $this->hasManyThrough(City::class,District::class);
}
}
02.
<?php
namespace App\Hrm;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class District extends Model
{
//use SoftDeletes;
protected $fillable = ['country_id','name','description','status'];
public function country(){
return $this->belongsTo(Country::class);
}
public function cities(){
return $this->hasMany(City::class);
}
}
3.
namespace App\Hrm;
use App\User;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class City extends Model
{
//use SoftDeletes;
protected $fillable = ['district_id','name','description','status'];
public function district(){
return $this->belongsTo(District::class);
}
// public function country(){
// return $this->hasOneThrough(Country::class, District::class);
// }
Doesn't look like there is a native way to define the inverse of a "hasManyThrough" relationship yet in Laravel. There have been a few issues opened on github to request it, but they were closed.
You could use the staudenmeir/belongs-to-through package if you don't mind installing a third-party package for this functionality. Then you should be able to define a belongsToThrough relationship like this:
class City extends Model
{
use \Znck\Eloquent\Traits\BelongsToThrough;
public function country() {
return $this->belongsToThrough(Country::class, District::class);
}
}
Why can't use parent method?
$city = City::find(1);
$country = $city->district->country();
i just had a similar situation i was able to accomplish a belongsToThrough with hasOneThrough
public function country()
{
return $this->hasOneThrough(
Country::class, // model we are trying to get
District::class, // model we have an _id to
'id', // WHERE `district`.`id` = `city`.`district_id`
'id', // `countries`.`id`
'district_id', // local column relation to our through class
'country_id' // `district`.`country_id`
);
}
what this should generate is
SELECT * FROM `countries`
INNER JOIN `districts`
ON `districts`.`country_id` = `countries`.`id`
WHERE `districts`.`id` = ?
-- ? == city.district_id
Database structure:
City:
id: increments
district_id: integer
...
Country:
id: increments
...
District:
id: increments
country_id: integer
...
we can then do $city->country
note: i have not fully tested this but with the testing that i have done it 'works'
Edit: i originally thought that i needed to leave the localKey
parameter null otherwise the relation wont work. it turns out i didnt
fully understand what that column was doing and that was wrong. That
key is the local column that relates to our through column (unless i
still have more to learn/figure out), when left the value as null, it
would use the local id column which a. is the wrong value, b. can also
be out of range (which is how i discovered it was using the wrong
value)
in my testing i only had two rows, both with the same relations. what
i didnt realize though was that on the "through table" both row 1 and
2 and the same related (relation where are trying to reach) so i didnt
notice the issue right away. hopefully now its all working

Laravel Eloquent Model change table name on runtime in a related instance

I am new to Laravel and also asked the question on Laracast without any success so far.
Here is my problem: I have a database layout something like this:
Table: categoryA_products
Table: categoryB_products
Table: categoryC_products
and per default the Laravel user table:
Table: user
I have create a two Laravel Eloquent models:
Product:
namespace App;
use Illuminate\Database\Eloquent\Model;
class Product extends Model
{
// protected $table = '';
public function users()
{
return $this->belongsTo( User::class );
}
}
User:
namespace App;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
public function products()
{
return $this->hasMany( Product::class );
}
}
As each product has a different table name I would normally create 1 model for each table but as they are all similar I would like to define the model table name at runtime.
I know I can do this with "$product->setTable()" but as I use the "newRelatedInstance" class from Laravel (hasMany and belongsTo) I cannot initiate the product class and set the table.
Is there a workaround for this?
Yes, I am aware that I could create a category table and link the products to each category but this is a fictional database model. There is a reason for this approach and I can explain it more in detail if needed. That said it make sense for this sample but I cannot use it for the live database.
I have a working solution with a model for each "category" but this is very messy.
Any help would be appreciated.
Since you're unable to load the relations, you could try referencing and re-initializing them like:
$relations = $product->getEagerLoads();
$attributes = $product->getOriginal();
table_name = 'categoryA_products'; // or categoryB_products or categoryC_products
$product->newQuery()
->newModelInstance($attributes)
->setTable($table_name)
->setEagerLoads($relations)
->...

getting the value of an extra pivot table column laravel

I have a phone_models, phone_problems, and a phone_model_phone_problem pivot table. The pivot table has an extra column 'price'.
PhoneModel:
class PhoneModel extends \Eloquent
{
public function problems()
{
return $this->belongsToMany('RL\Phones\Entities\PhoneProblem')->withPivot('price');
}
}
PhoneProblem:
class PhoneProblem extends \Eloquent
{
public function models()
{
return $this->belongsToMany('PhoneModel')->withPivot('price');
}
}
What I'm trying to do is get the price of a specific phone with a specific problem.
This is how I have it now but I feel like Laravel has a built in Eloquent feature I can't find to do this in a much simpler way:
$model = $this->phoneService->getModelFromSlug($model_slug);
$problem = $this->phoneService->getProblemFromSlug($problem_slug);
all this does is select the specific model and problem from their slug.
then what I do is with those credentials I get the price like so:
$row = DB::table('phone_model_phone_problem')
->where('phone_model_id', '=', $model->id)
->where('phone_problem', '=', $problem->id)
->first();
so now I can get the price like so $row->price but I feel like there needs to be a much easier and more 'Laravel' way to do this.
When using Many to Many relationships with Eloquent, the resulting model automatically gets a pivot attribute assigned. Through that attribute you're able to access pivot table columns.
Although by default there are only the keys in the pivot object. To get your columns in there too, you need to specify them when defining the relationship:
return $this->belongsToMany('Role')->withPivot('foo', 'bar');
Official Docs
If you need more help the task of configuring the relationships with Eloquent, let me know.
Edit
To query the price do this
$model->problems()->where('phone_problem', $problem->id)->first()->pivot->price
To get data from pivot table:
$price = $model->problems()->findOrFail($problem->id, ['phone_problem'])->pivot->price;
Or if you have many records with different price:
$price = $model->problems()->where('phone_problem', $problem->id)->firstOrFail()->pivot->price;
In addition.
To update data in the pivot you can go NEW WAY:
$model->problems()->sync([$problemId => [ 'price' => $newPrice] ], false);
Where the 2nd param is set to false meaning that you don't detach all the other related models.
Or, go old way
$model->problems()->updateExistingPivot($problemId, ['price' => $newPrice]);
And remind you:
To delete:
$model->problems()->detach($problemId);
To create new:
$model->problems()->attach($problemId, ['price' => 22]);
It has been tested and proved working in Laravel 5.1 Read more.
Laravel 5.8~
If you want to make a custom pivot model, you can do this:
Account.php
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Account extends Model
{
public function users()
{
return $this->belongsToMany(User::class)
->using(AccountUserPivot::class)
->withPivot(
'status',
'status_updated_at',
'status_updated_by',
'role'
);
}
}
AccountUserPivot.php
<?php
namespace App;
use Illuminate\Database\Eloquent\Relations\Pivot;
class AccountUserPivot extends Pivot
{
protected $appends = [
'status_updated_by_nice',
];
public function getStatusUpdatedByNiceAttribute()
{
$user = User::find($this->status_updated_by);
if (!$user) return 'n/a';
return $user->name;
}
}
In the above example, Account is your normal model, and you have $account->users which has the account_user join table with standard columns account_id and user_id.
If you make a custom pivot model, you can add attributes and mutators onto the relationship's columns. In the above example, once you make the AccountUserPivot model, you instruct your Account model to use it via ->using(AccountUserPivot::class).
Then you can access everything shown in the other answers here, but you can also access the example attribute via $account->user[0]->pivot->status_updated_by_nice (assuming that status_updated_by is a foreign key to an ID in the users table).
For more docs, see https://laravel.com/docs/5.8/eloquent-relationships (and I recommend press CTRL+F and search for "pivot")

Laravel Soft Delete posts

in our project we must be use soft delete for each posts. in laravel document i think we can only use this feature for tables.
can we use that for posts on table such as
$id = Contents::find($id);
$id->softDeletes();
Updated Version (Version 5.0 & Later):
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class Post extends Model {
use SoftDeletes;
protected $table = 'posts';
// ...
}
When soft deleting a model, it is not actually removed from your
database. Instead, a deleted_at timestamp is set on the record. To
enable soft deletes for a model, specify the softDelete property on
the model (Documentation).
For (Version 4.2):
use Illuminate\Database\Eloquent\SoftDeletingTrait; // <-- This is required
class Post extends Eloquent {
use SoftDeletingTrait;
protected $table = 'posts';
// ...
}
Prior to Version 4.2 (But not 4.2 & Later)
For example (Using a posts table and Post model):
class Post extends Eloquent {
protected $table = 'posts';
protected $softDelete = true;
// ...
}
To add a deleted_at column to your table, you may use the softDeletes
method from a migration:
For example (Migration class' up method for posts table) :
/**
* Run the migrations.
*
* #return void
*/
public function up()
{
Schema::create('posts', function(Blueprint $table)
{
$table->increments('id');
// more fields
$table->softDeletes(); // <-- This will add a deleted_at field
$table->timeStamps();
});
}
Now, when you call the delete method on the model, the deleted_at column will be set to the current timestamp. When querying a model that uses soft deletes, the "deleted" models will not be included in query results. To soft delete a model you may use:
$model = Contents::find( $id );
$model->delete();
Deleted (soft) models are identified by the timestamp and if deleted_at field is NULL then it's not deleted and using the restore method actually makes the deleted_at field NULL. To permanently delete a model you may use forceDelete method.
You actually do the normal delete. But on the model you specify that its a softdelete model.
So on your model add the code:
class Contents extends Eloquent {
use SoftDeletingTrait;
protected $dates = ['deleted_at'];
}
Then on your code do the normal delete like:
$id = Contents::find( $id );
$id ->delete();
Also make sure you have the deleted_at column on your table.
Or just see the docs: http://laravel.com/docs/eloquent#soft-deleting
I just did this with Laravel 8 and it worked. It's basically what #The alpha said, but trying to wrap everything quicker. Follow this steps.
In the migration file add:
$table->softDeletes();
In the model:
use Illuminate\Database\Eloquent\SoftDeletes;
class User extends Model
{
use SoftDeletes;
...
];
}
In the controller:
$user->delete();
Bonus: if you need to restore the deleted user
User::withTrashed()->find($id);->restore();
Just an update for Laravel 5:
In Laravel 4.2:
use Illuminate\Database\Eloquent\SoftDeletingTrait;
class Post extends Eloquent {
use SoftDeletingTrait;
protected $dates = ['deleted_at'];
}
becomes in Laravel 5:
use Illuminate\Database\Eloquent\SoftDeletes;
class User extends Model {
use SoftDeletes;
protected $dates = ['deleted_at'];
In Laravel 5.5 Soft Deleted works ( for me ).
Data Base
deleted_at Field, default NULL value
Model
use Illuminate\Database\Eloquent\SoftDeletes;
class User extends Model {
use SoftDeletes;
}
Controller
public function destroy($id)
{
User::find($id)->delete();
}
In the Latest version of Laravel i.e above Laravel 5.0. It is quite simple to perform this task.
In Model, inside the class just write 'use SoftDeletes'. Example
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class User extends Model
{
use SoftDeletes;
}
And In Controller, you can do deletion. Example
User::where('email', 'youremail#example.com')->delete();
or
User::where('email', 'youremail#example.com')->softDeletes();
Make sure that you must have 'deleted_at' column in the users Table.
Here is the details from laravel.com
http://laravel.com/docs/eloquent#soft-deleting
When soft deleting a model, it is not actually removed from your database. Instead, a deleted_at timestamp is set on the record. To enable soft deletes for a model, specify the softDelete property on the model:
class User extends Eloquent {
protected $softDelete = true;
}
To add a deleted_at column to your table, you may use the softDeletes method from a migration:
$table->softDeletes();
Now, when you call the delete method on the model, the deleted_at column will be set to the current timestamp. When querying a model that uses soft deletes, the "deleted" models will not be included in query results.

Categories