How to remove a polymorphic relation in Eloquent? - php

I have a model like this:
<?php
class Post extends Eloquent {
protected $fillable = [];
public function photos()
{
return $this->morphMany('Upload', 'imageable');
}
public function attachments()
{
return $this->morphMany('Upload', 'attachable');
}
}
and my morphMany table's schema is like this:
CREATE TABLE `uploads` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`raw_name` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`name` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`size` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`downloads` int(11) NOT NULL DEFAULT '0',
`imageable_id` int(11) DEFAULT NULL,
`imageable_type` varchar(32) COLLATE utf8_unicode_ci NOT NULL,
`attachable_id` int(11) DEFAULT NULL,
`attachable_type` varchar(32) COLLATE utf8_unicode_ci NOT NULL,
`user_id` int(10) unsigned NOT NULL,
`created_at` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
`updated_at` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
PRIMARY KEY (`id`),
KEY `uploads_user_id_index` (`user_id`),
CONSTRAINT `uploads_user_id_foreign` FOREIGN KEY (`user_id`) REFERENCES `posts` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=45 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
now I need to remove one row of this table, I tried $posts->photos()->delete(); but it removed all rows associated to this Post.
Could someone help me?

$posts->photos() is the relationship query to return all of the photos for a post. If you call delete() on that, it will delete all of those records. If you only want to delete a specific record, you need to make sure you only call delete on the one you want to delete. For example:
$posts->photos()->where('id', '=', 1)->delete();

to reomove from pivot table in many to many polymorphic relation just use detach:
$posts->photos($photoModel)->detach();

The relationship isn't even needed for this. Just use the Upload model directly:
Upload::find($id)->delete();
Or even shorter:
Upload::destroy($id);

Related

How to access to sub related model when the related model returns a collection

I have 3 models: User, Payment and Log. A User has many Payment and both User and Payment have many Log.
User Model
class User
{
public function payments()
{
return $this->hasMany('Payment', 'user_id');
}
public function logs()
{
return $this->morphMany(Log::class, 'loggable');
}
}
users table
CREATE TABLE `users` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`email` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`email_verified_at` timestamp NULL DEFAULT NULL,
`password` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`remember_token` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `users_email_unique` (`email`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
Payment Model
class Payment
{
public function user()
{
return $this->belongsTo('User', 'user_id');
}
public function logs()
{
return $this->morphMany(Log::class, 'loggable');
}
}
payments table
CREATE TABLE `payments` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`status` varchar(50),
`amount` int(11) NOT NULL,
`collection_date` date NOT NULL,
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
`user_id` bigint(20) unsigned NOT NULL,
PRIMARY KEY (`id`),
CONSTRAINT `fk_payments_user_id` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
Log Model
class Log
{
public function loggable()
{
return $this->morphTo();
}
}
logs table
CREATE TABLE `logs` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`loggable_type` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`loggable_id` bigint(20) unsigned NOT NULL,
`old_values` text COLLATE utf8mb4_unicode_ci,
`new_values` text COLLATE utf8mb4_unicode_ci,
`user_id` bigint(20) unsigned DEFAULT NULL, /* the user that made the change, if any */
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
The Log model stores all changes made to any other model (it's a polymorphic relationship), so if the user changes its name, the Log model will store the older name and the new name. The same applies to Payment: if a payment status changes the Log model will have a new record with the old status and the new status.
I need to show a paginated list of all Log records for a specific User ordered by date. So my code is:
$user = App\User::find($id);
$allLogs = $user->logs();
// Now I need to join (I'm using union) both sets of logs
$allLogs->union($user->payments->logs());
However, since a User can have many Payment, $user->payments returns a Collection, so is no longer a query builder/eloquent object and it fails when I try to call ->logs().
$user->payments()->logs() also doesn't work, because $user->payments() returns a HasMany object and the ->logs() method doesn't exist.
I'm trying to avoid getting each collection of Log separately and then processing them using php (it would be perfect to delegate that task to MySql).
I believe it can be done, because I can write the query on MySql:
select l.*
from payments p
join logs l on p.id = l.loggable_id and l.loggable_type = 'App\\Payments'
where p.user_id = SOMEUSERID
Thanks in advance
Eager load the relations(reduces number of queries)
$user = User::with(['payments.logs', 'logs'])->find($id);
Query using the Log model.
$logs = Log::where([
'loggable_id' => $user->id,
'loggable_type' => 'User',
])
->orWhere(function($query){
$query->whereIn('loggable_id',
$user->payments()->pluck('id'))
->where('loggable_type', 'Payment');
})->get();
OR
Get them individually and then combine them.
$all_logs = collect([]);
$all_logs->push($user->logs);
foreach($user->payments as $p){
$all_logs->push($p->logs);
}
$final_logs = $all_logs->collapse();
OR
Just use the relations, without iterating over the payments. You can combine the results if you want(as shown in the previous approach).
$user_logs = $user->logs;
$payment_logs = $user->payments->pluck('logs')->collapse();

Laravel Morph Many from two columns

I need to find a way to modify the morphMany relationship to morph two different columns. Here is the table create syntax that is being morphed:
CREATE TABLE `user_friendships` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`sender_type` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`sender_id` bigint(20) unsigned NOT NULL,
`recipient_type` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`recipient_id` bigint(20) unsigned NOT NULL,
`status` tinyint(4) NOT NULL DEFAULT '0',
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `user_friendships_sender_type_sender_id_index` (`sender_type`,`sender_id`),
KEY `user_friendships_recipient_type_recipient_id_index` (`recipient_type`,`recipient_id`)
) ENGINE=InnoDB AUTO_INCREMENT=12332 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
Sender and recipient can both be a user. So I want to morph based on whatever column of the two contains the user_id.
This only looks at if the sender is the user, but not the recipient.
return $this->morphMany(Friendship::class, 'sender');
The output of the query from that is:
select * from `user_friendships` where `user_friendships`.`sender_id` = 5971 and `user_friendships`.`sender_id` is not null and `user_friendships`.`sender_type` = "App\\\User"
What we actually want is:
select * from `user_friendships` where (`user_friendships`.`sender_id` = 5971 and `user_friendships`.`sender_id` is not null and `user_friendships`.`sender_type` = "App\\\User") OR (`user_friendships`.`recipient_id` = 5971 and `user_friendships`.`recipient_id` is not null and `user_friendships`.`recipient_type` = "App\\\User")
How do I accomplish this?
https://github.com/staudenmeir/laravel-merged-relations
This composer package was able to solve it.

Laravel - how to eager load relation of custom intermediate table

I have three models, SalesReturn, Product and ProductSalesReturn, and their relations are following:
class SalesReturn extends Model
{
public function products()
{
return $this->belongsToMany(Product::class)
->withTimestamps()
->using(ProductSalesReturn::class);
}
}
I use the ProductSalesReturn to represent the intermediate table ( https://laravel.com/docs/6.x/eloquent-relationships#defining-custom-intermediate-table-models ), and ProductSalesReturn has a relation to Unit:
class ProductSalesReturn extends Pivot
{
public function unit()
{
return $this->belongsTo(Unit::class);
}
}
When I eager loading the unit relation like following code:
SalesReturn::with(['products', 'products.unit'])->find($id);
I will get the following error:
SQLSTATE[42S22]: Column not found: 1054 Unknown column 'units.product_id' in 'where clause' (SQL: select * from `units` where `units`.`product_id` in (1031, 1631, 13391, 14361, 16981, 17441, 41982, 45982, 55741) and `units`.`deleted_at` is null)
The table schemas are following:
CREATE TABLE `sales_returns` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`number` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL,
`user_id` bigint(20) NOT NULL,
`order_id` bigint(20) NOT NULL,
`note` text COLLATE utf8mb4_unicode_ci NOT NULL,
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE `product_sales_return` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`sales_return_id` bigint(20) NOT NULL,
`product_id` bigint(20) NOT NULL,
`unit_id` bigint(20) NOT NULL,
`price` double NOT NULL,
`amount` int(11) NOT NULL,
`gross_profit` double NOT NULL DEFAULT '0',
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=17 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
How can I eager loading the relation of custom intermediate table in Laravel ?
Thank you.
You could use Laravel Eloquent: Eager Load Pivot Relations
Installation
composer require ajcastro/eager-load-pivot-relations
Configuration
use AjCastro\EagerLoadPivotRelations\EagerLoadPivotTrait;
class Product extends Model
{
// Use the trait here to override eloquent builder.
// It is used in this model because it is the relation model defined in
// SalesReturn::products() relation.
use EagerLoadPivotTrait;
}
Usage
return SalesReturn::with('products.pivot.unit')->get();
Define the table, foreignPivotKey and relatedPivotKey paramethers in your produtcts belongsToMany relationship

Persistent Integrity Constraint Laravel

I'm still new to understanding relationships and the MVC. But I tried to follow everything I've read online and still receives this error.
Integrity constraint violation: 1452 Cannot add or update a child row: a foreign key constraint fails (`guest_log`.`logs`, CONSTRAINT `fk_logs_guests1` FOREIGN KEY (`guest_id`) REFERENCES `guests` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION)
Here is the SQL for guest, log and department
DROP TABLE IF EXISTS `Dept`;
CREATE TABLE `Dept` (
`dept_id` int(5) NOT NULL AUTO_INCREMENT,
`dept_code` varchar(30) CHARACTER SET latin1 DEFAULT NULL,
`dept_description` varchar(75) CHARACTER SET latin1 NOT NULL,
`dept_assign` enum('Academic','Admin') CHARACTER SET latin1 NOT NULL DEFAULT 'Academic',
`is_deleted` enum('Yes','No') CHARACTER SET latin1 DEFAULT 'No',
PRIMARY KEY (`dept_id`)
)
-- ----------------------------
-- Table structure for guests
-- ----------------------------
DROP TABLE IF EXISTS `guests`;
CREATE TABLE `guests` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`first_name` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`middle_name` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`last_name` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`gender` varchar(6) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`email` varchar(45) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`tel_no` varchar(45) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`mobile_no` varchar(45) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`company` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`photo` varchar(500) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `id_UNIQUE` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- ----------------------------
-- Table structure for logs
-- ----------------------------
DROP TABLE IF EXISTS `logs`;
CREATE TABLE `logs` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`guest_id` int(11) NOT NULL,
`guest_id_number` datetime NOT NULL,
`date_time_in` datetime NOT NULL,
`date_time_out` datetime DEFAULT NULL,
`purpose` varchar(45) COLLATE utf8mb4_unicode_ci NOT NULL,
`department_to_visit` int(11) DEFAULT NULL,
PRIMARY KEY (`id`,`guest_id`),
KEY `fk_logs_guests1_idx` (`guest_id`),
KEY `fk_logs_Dept1_idx` (`department_to_visit`),
CONSTRAINT `fk_logs_guests1` FOREIGN KEY (`guest_id`) REFERENCES `guests` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION,
CONSTRAINT `fk_logs_Dept1` FOREIGN KEY (`department_to_visit`) REFERENCES `Dept` (`dept_id`) ON DELETE NO ACTION ON UPDATE NO ACTION
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
While here are the models.
class Guest extends Model
{
protected $table = 'guests';
public function logs()
{
return $this->hasMany('App\Log');
}
}
class Log extends Model
{
protected $table = 'logs';
public function guests()
{
return $this->belongsTo('App\Guest','guest_id','id');
}
public function departments()
{
return $this->belongsTo('App\Department','department_to_visit','dept_id');
}
}
class Department extends Model
{
protected $table = 'dept';
public function logs()
{
return $this->hasMany('App\Log','department_to_visit','dept_id');
}
}
Whenever I try to create a new entry for Log, I received this Integrity Constraint Violation error. Please help me resolved and understand why I'm encountering this problem.
UPDATE:
code to create log entry
<div class="container-fluid">
{!! BootForm::inline(['route'=>['logs.store'],'method'=>'POST']) !!}
{!! BootForm::select('guest_id', 'Guest Name',\App\Guest::pluck('last_name','id'),null, [])!!}
{!! BootForm::tel('guest_id_number','ID Number',null, ['required']) !!}
{!! BootForm::select('department_to_visit','Department to Visit', App\Department::pluck('dept_description','dept_id'),null,[]) !!}
{!! BootForm::text('purpose',null,null,['required']) !!}
{!! BootForm::submit('Save') !!}
{!! BootForm::close() !!}
</div>
log.store method
public function store(Request $request)
{
$this->validate($request, [
'guest_id'=>'required',
'guest_id_number'=>'required',
'purpose'=>'required',
'department_to_visit']);
$logs = new Log();
$logs->guest_id = $request->guest_id;
$logs->guest_id_number = $request->guest_id_number;
$logs->purpose = $request->purpose;
$logs->department_to_visit = $request->department_to_visit;
$logs->date_time_in = Carbon::now();
$logs->save();
return redirect(route('home'));
}
It could be you are not passing and existing guest id.
To check it normally I search the model, for eg.
$guest = Guest::findOrFail($request->guest_id);
and use
$logs->guest_id = $guest->id;
Try this and let me know how it works :)

Eloquent Join With Pivot Table?

I have a pivot table called message_recipients that looks like
CREATE TABLE `message_recipients` (
`user_id` bigint(20) unsigned NOT NULL,
`message_id` bigint(20) unsigned NOT NULL,
KEY `message_recipients_user_id_index` (`user_id`),
KEY `message_recipients_message_id_index` (`message_id`),
CONSTRAINT `message_recipients_message_id_foreign` FOREIGN KEY (`message_id`) REFERENCES `messages` (`id`),
CONSTRAINT `message_recipients_user_id_foreign` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
And then I have a messages table
CREATE TABLE `messages` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`sender_id` bigint(20) unsigned NOT NULL,
`subject` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`message` longtext COLLATE utf8_unicode_ci NOT NULL,
`status` tinyint(4) NOT NULL,
`created_at` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
`updated_at` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
`deleted_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `messages_sender_id_index` (`sender_id`),
CONSTRAINT `messages_sender_id_foreign` FOREIGN KEY (`sender_id`) REFERENCES `users` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=110 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
And the default user table.
And I have the following classes
class MessageRecipients extends Eloquent {
protected $table = 'message_recipients';
public $timestamps = false;
public function message() {
return $this->hasMany('App\Models\Message');
}
public function user() {
return $this->belongsTo('App\Models\User');
}
public function me() {
return $this->belongsTo('App\Models\User');
}
}
class Message extends Eloquent {
public function recipients() {
return $this->hasMany('App\Models\MessageRecipients');
}
public function sender() {
return $this->belongsTo('App\Models\User');
}
}
And I am trying to figure out how to generate the query. I have
$message = Message::with('App\Models\MessageRecipients')->where('message_recipients.user_id', '=', 1)->first();
But I keep getting an error about missing message_recipients column. How can I generate a query that would look like (or produce the same data as)
SELECT
*
FROM
messages
JOIN
message_recipients
ON
messages.id = message_recipients.message_id
WHERE
message_recipients.user_id = 1
Or basically select all the messages with message_recipients data that are assigned to that user.

Categories