I am having this same issue. Exactly the same issue accept different class and table names. Does anybody have some insight?
Site:
/**
* #return \Illuminate\Database\Eloquent\Relations\BelongsToMany
*/
public function categoryBlocks()
{
return $this->belongsToMany(Category::class, 'blocked_category');
}
Category:
/**
* #return \Illuminate\Database\Eloquent\Relations\BelongsToMany
*/
public function sites()
{
return $this->belongsToMany(Site::class, 'blocked_category');
}
The query properly generates when querying from category to site but not from site to category. (I have tried renaming the method on the site Model but it doesn't help at all.)
$catBlocks = $category->sites()->get(); // Query creates a category_id value in the query
$blocks = $site->categoryBlocks()->get(); // Query doesn't create a site_id value in the query
Table: blocked_category
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`site_id` int(10) unsigned NOT NULL,
`category_id` int(10) unsigned NOT NULL,
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
Table: sites
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`publisher_id` int(10) unsigned NOT NULL,
`name` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`url` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
`deleted_at` timestamp NULL DEFAULT NULL,
`status_id` int(11) NOT NULL DEFAULT '1',
Table: categories
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
Try passing a third argument to the categoryBlocks relationship of 'category_id':
public function categoryBlocks()
{
return $this->belongsToMany(Category::class, 'blocked_category', 'category_id');
}
Or whatever you have called the category_id within the blocked_category table. If this doesn't work please reply with your database schema.
So I found the issue. The instance of the object being used to query wasn't being fully hydrated which was causing the query builder not to see the relationship properly.
I was getting the instance of the object passed into the method, but for some reason it wasn't a fully hydrated instance, so I just had to manually hydrate the Site object before running the query.
Related
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();
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.
In controller I am trying to get data from table, and then pass it to the view.
//controller
public function view($id)
{
$contact = Contact::where('id', $id);
return view('contact.edit', ['contact' => $contact]);
}
//view
#foreach($contact as $key=>$value)
{{ $value }}
#endforeach
But I get just empty page, and if I try to echo value with {{ $contact->first_name}} i just receive error Undefined property: Illuminate\Database\Eloquent\Builder::$first_name.
I also tried to use find method, but got the same result.
Table schema:
CREATE TABLE `contacts` (
`id` int(11) NOT NULL,
`first_name` varchar(25) NOT NULL,
`last_name` varchar(25) NOT NULL,
`email` varchar(35) NOT NULL,
`home_phone` int(10) DEFAULT NULL,
`work_phone` int(10) DEFAULT NULL,
`cell_phone` int(10) DEFAULT NULL,
`best_phone` enum('home_phone','work_phone','cell_phone') NOT NULL,
`address_1` varchar(100) DEFAULT NULL,
`address_2` varchar(100) DEFAULT NULL,
`city` varchar(35) DEFAULT NULL,
`state` varchar(35) DEFAULT NULL,
`zip` int(6) DEFAULT NULL,
`country` varchar(35) DEFAULT NULL,
`birth_date` date DEFAULT NULL,
`manager` int(11) UNSIGNED NOT NULL
);
Contact::where('id', $id) only prepares a query with the query builder. You haven't yet executed the query or retrieved any results.
Contact::where('id', $id)->first() will execute the query and retrieve the first result, although since id is likely the primary key, Contact::find($id) is a quicker way to get the first result matching that id.
EDIT: Might be because you're not using ->first(); after the query.
Not really sure why that wouldn't work, you can try this instead:
public function view($id)
{
$contact = Contact::find($id);
return view('contact.edit')->with('contact', $contact);
}
Then in your view you can access all properties like so:
{{$contact->id}}
{{$contact->first_name}}
//etc
I am trying to add a new integer "id" column in a table of my database to map every row with an id. In order to do so, I am using a migration in Laravel 5.1. The run() function of the migration is exactly the following:
public function up()
{
Schema::table('license_keys', function($table) {
$table->integer('id_nuevo');
});
}
The table I am trying to modify is set with default 'updated_at' and 'created_at' timestamps.
I execute the migration and the column is added correctly. I made sure to add the new column in the $fillable variable in my model. The next step in the process is to set the ids correctly, because that column is created with all 0s. In order to do so, I am using a Seeder with the following code:
public function run()
{
$i=1;
$licenses = LicenseKey::getEveryLicenseKey();
foreach ($licenses as $license){
$license->id_nuevo = $i;
//$license->timestamps = false;
$license->save();
$i++;
}
}
But the problem starts here. When I try to update any field of any row with the save() function it gives me the following error:
[Illuminate\Database\QueryException]
SQLSTATE[21S01]: Insert value list does not match column list: 1136 Column count doesn't match value count at row 1 (SQL: update `license_keys` set `id_nuevo` = 1, `updated_at` = 2018-11-15 13:24:11
where `hash_key` = 0...0b)
[PDOException]
SQLSTATE[21S01]: Insert value list does not match column list: 1136 Column count doesn't match value count at row 1
But if I set the timestamps to false (commented line in the code), the operation succeeds. Even if I try to manually change the value of the 'updated_at' column in phpMyadmin, it gives me the same error. Can anybody help me with this problem?
The structure of the table is:
CREATE TABLE `license_keys` (
`hash_key` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`user_id` int(10) UNSIGNED NOT NULL,
`plan_id` int(10) UNSIGNED NOT NULL,
`id_nuevo` int(11) NOT NULL,
`max_credits` bigint(20) NOT NULL,
`max_models` int(11) NOT NULL,
`max_model_categories` int(11) NOT NULL,
`max_dictionaries` int(11) NOT NULL,
`max_dictionary_entries` int(11) NOT NULL,
`max_sentiment_models` int(11) NOT NULL,
`max_sentiment_entries` int(11) NOT NULL,
`current_credits` bigint(20) NOT NULL,
`current_requests` bigint(20) NOT NULL,
`last_operation` timestamp NULL DEFAULT NULL,
`total_credits` bigint(20) NOT NULL,
`total_requests` bigint(20) NOT NULL,
`msg_pay_status` varchar(510) COLLATE utf8_unicode_ci NOT NULL,
`start_date` timestamp NULL DEFAULT NULL,
`start_billing_date` timestamp NULL DEFAULT NULL,
`expiration_date` timestamp NULL DEFAULT NULL,
`update_operation` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,,
`updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
And the query I'm trying to execute is:
update `license_keys` set `id_nuevo` = 1, `updated_at` = '2018-11-15 13:24:11' where `hash_key` = '0...0b'
Thanks.
Try your query without update_at, it will be written automatically
update `license_keys` set `id_nuevo` = 1 where `hash_key` = '0...0b'
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);