I'm creating project with Laravel. I want to pull out user->name from subscription table which also has user_id column in it. I want to show the current user's subscribers' name from subscription table. But it shows error:
Property [subs] does not exist on this collection instance
here is my code.
This is from IndexController
public function home() {
$data = User::all();
return view("amvs.index", [
'users' => $data,
]);
}
This is from index.blade.php
Subscriptions
#foreach ($users->subs as $sub)
<span class="ms-2">{{$subs->name}}</span>
#endforeach
This is from user model
public function sub(){
return $this->hasMany('App\Models\Subscription');
}
I didn't write anything in Subscription model. My subscription table
Schema::create('subscriptions', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->integer('user_id');
$table->timestamps();
});
My user table
Schema::create('users', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('email')->unique();
$table->timestamp('email_verified_at')->nullable();
$table->string('password');
$table->rememberToken();
$table->timestamps();
});
I'm a beginner so enlighten me.
The issue is that you're trying to access a method on an Eloquent Collection which does not have any properties or methods relatated to your User or its sub relationship.
Additionally, consider using Eager Loading when accessing relationships, it is more performant and solves the N+1 query problem.
public function home() {
// access the authenticated User and load their subs relationship
$user = Auth::user()->with('subs')->first();
return view("amvs.index", [
'user' => $user
]);
}
Then to access the Users subs in your view:
#foreach ($user->subs as $sub)
{
<span class="ms-2">{{$sub->name}}</span>
}
However, you will want to change your subscriptions migration as the user_id field needs to be the same type as the id field on your users table.
Schema::create('subscriptions', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->foreignId('user_id');
$table->timestamps();
});
Further questions
don't know why I should use foreignId?
For relationships to work, the field type (string, integer etc.) of the primary and foreign key fieds must be the same. The default primary key in Laravel is the id field and it has as type of unsigned big integer. If you define a foreign key field in a related table and its type is anything other than unsignedBigInteger the relationship will fail.
foreignId is simply a helper method provided by Laravel that creates a field with the correct type and the required foreign key reference. Previously you had to do that all yourself, for example:
$table->unsignedBigInteger('user_id');
$table->foreign('user_id')->references('id')->on('users');
why you used first()?
The reason for using first() is that it returns a Model rather than an Eloquent Collection. You're only interested in one User (the authenticated User) so it is more efficient to use first() rather than get() as you don't then need to loop or extract the first item in the collection.
You might want to spend some free time watching the Laravel from Scratch series. It is really detailed and covers lots of common scenarios.
First of all, In your controller you should load the subs. That will be better as it will not add extra query for loading the relations. So what you can do is following
$data = User::with('subs')->all();
you should also change the relationship as hasMany follows the following convention:
public function subs(){
return $this->hasMany('App\Models\Subscription');
}
As you are pulling all the users you have to first loop through the users first, then you can iterate over subs for each users like below:
#foreach ($users as $user)
#foreach ($user->subs as $sub)
<span class="ms-2">{{$subs->name}}</span>
#endforeach
#endforeach
Related
I am confused about Laravel database relationship (unsigned id, foreign references, cascade).
Do I have to use relationship in class (like hasMany, hasOne) and in table migration (like foreign, references); for both of them too?
I had read some articles but they are not clear for me. what is the best way for best developing on an example?
As an example for category and blog post; how should it be or your best example please?
create_categories_table migration:
public function up()
{
Schema::create('categories', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('slug');
$table->timestamps();
});
}
create_posts_table migration:
public function up()
{
Schema::create('posts', function (Blueprint $table) {
$table->id();
$table->string('title');
$table->string('slug');
$table->longText('description');
$table->timestamps();
});
}
and Post Model:
public function category()
{
return $this->belongsTo(Category::class);
}
thank you.
Technically you don't have to define relationships between related data in your database, however, it is good practice to do so and provides benefits such as data integrity and cascading. There are also tools that can reverse engineer your database to generate a visual representation of its structure including relationships if they have been defined.
Adding relationships in your migration files creates that relationship at the database level, informing the engine that there is a logical relationship between data. You can define a foreign key constraint in your migrations in many ways, however, as of Laravel 7.x there is a forieignId method and a constrained method providing a simplified way of defining relationships from the previous method.
So for a basic example, to define a simple one-to-one relationship you might do:
public function up()
{
Schema::create('child', function (Blueprint $table) {
$table->foreignId('parent_id')->constrained();
});
}
Similarly, using Eloquent relationships to define relationships at the application level is not required. However, these helper methods provide a consistent and simplified implementation for managing related data that would otherwise require you to write additional code.
At least one but ideally both of the above would be used.
Update
if we need set a foreign key as a different key the using is just like
->constrained('privateName_categories'). two basic questions: * how Laravel understands that the reference table is 'categories' on your
example without writing its name?
The argument provided to constrained is not the name of the foreign key field but the name of the table the foreign key references. This is used in instances that either your foreign key or the table it references differ from the standard Laravel naming conventions and therefore cannot be inferred.
Laravel uses naming conventions to make connections between things. For foreign keys that convention has the format {table}_{id}. Laravel inspects the foreign key name and then uses the {table} element of the convention to create the relationship behind the scenes. If you're not using conventions you need to inform Laravel.
The following uses conventions to automatically create a relationship between the posts and categories table based on the value of the foreignId method argument.
public function up()
{
Schema::create('posts', function (Blueprint $table) {
$table->foreignId('category_id')->constrained();
});
}
This example doesn't use a standard foreign key naming convention and so the related table needs to be provided to constrained.
public function up()
{
Schema::create('posts', function (Blueprint $table) {
$table->foreignId('another_field')->constrained('categories');
});
}
Similarly, for scenarios in that you're not using conventions, you might need to specify the related column names in your Eloquent Model relationship. The exact syntax for this differs based on the type of relationship (hasOne, hasMany, manyToMany, etc.). You can read up on the different syntaxes for each relationship in Laravels defining relationships documentation.
you mention about reverse engineering tools
DataGrip by JetBrains can inspect the schema of your database and produce a visual representation. Others are also available, just do a search of the internet.
The role of migrations is to build your database schema (INSERT TABLE, ALTER TABLE, FOREIGN KEYS, DROP COLUMN, etc...).
The role of relations in Eloquent Model is to make Model aware of relations with other models. It provides a convenient way to query related models.
Relations will not create foreign keys for you.
For example if a post can be in only one category and a category contains many posts :
public function up()
{
Schema::create('categories', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('slug');
$table->timestamps();
});
}
public function up()
{
Schema::create('posts', function (Blueprint $table) {
$table->id();
$table->unsignedInteger('category_id');
$table->string('title');
$table->string('slug');
$table->longText('description');
$table->timestamps();
$table->foreign('category_id')->references('id')->on('categories');
});
}
Then, in your Post model :
public function category()
{
return $this->belongsTo(Category::class);
}
If you need you can also add this to Category Model :
public function posts()
{
return $this->hasMany(Post::class);
}
This will allow you to easily retrieve all posts of a category for example :
public function getPostsByCategory(int $categoryId)
{
$posts = Category::find($categoryId)->posts()->get();
return $posts;
}
EDIT :
I think the main interest is when you want to eager load relations.
For example you have posts in a blog.
Each post have many categories. (a post can be in many categories).
Each category have many posts.
Each post have many comments
In homepage I want to display last 5 posts with for each post their categories and comments.
If I build well designed relations, I can do this:
public function getLatestPosts()
{
return Post::query()
->with(['categories', 'comments'])
->orderBy('created_at', 'desc')
->limit(5)
->get();
}
It will get last 5 posts and it will attach categories and comments to each Post model.
actually i have two kind of users which has two different table (user and seller table).
i have comment table with this fields:
Schema::create('comments', function (Blueprint $table) {
$table->increments('id');
$table->integer('user_id')->unsigned()->nullable();
$table->foreign('user_id')->references('id')->on('users');
$table->integer('parent_id')->unsigned()->default(0);
$table->text('comment_text');
$table->integer('commentable_id')->unsigned();
$table->string('commentable_type');
$table->timestamps();
});
how can I add seller_id to this table? if seller wants to response to a user comment.
same issue for message table.
Actually the good practice is you must add a role field in the user table that determines the user is a user or seller. But if you want to keep your table like that you don't need to add seller_id, just use one to many polymorphic relations. Change your comments table schema like this :
Schema::create('comments', function (Blueprint $table) {
$table->increments('id');
$table->integer('parent_id')->unsigned()->default(0);
$table->text('comment_text');
$table->integer('commentable_id')->unsigned();
$table->string('commentable_type');
$table->timestamps();
});
Then in the user and seller model, you must add the relationship method like this :
public function comments()
{
return $this->morphMany('App\Comment', 'commentable');
}
And in the comment model like this :
public function commentable()
{
return $this->morphTo();
}
Then you can get the seller comment like this :
$seller = App\Seller::find(1);
$seller->comments;
And to save the comment from the seller you can use this :
$seller = App\Seller::find(1);
$comment = $seller->comments()->create([
'comment_text' => 'A new comment.',
// Add other field
]);
I have a column name creator in the table storing the user id. When fetching records, I am using belongs to relation. I receive data, but I am not able to display it.
Note: This thread doesn't solve my issue
Category table scheme
Schema::create('categories', function (Blueprint $table) {
$table->bigIncrements('id');
$table->string('name')->unique();
$table->string('slug')->unique();
$table->string('banner')->nullable();
$table->boolean('status')->default(false);
$table->bigInteger('creator');
$table->bigInteger('moderator');
$table->timestamps();
});
user table scheme
Schema::create('users', function (Blueprint $table) {
$table->bigIncrements('id');
$table->string('name');
$table->string('email')->unique();
$table->timestamp('email_verified_at')->nullable();
$table->string('password');
$table->rememberToken();
$table->timestamps();
});
Category Model:
public function creator(){
return $this->hasOne(User::class, 'id', 'creator')->select('id', 'name');
}
Category Controller code:
$records = Category::with(['creator'])->paginate(env('REC_LIMIT'));
data I get it:
"data":[{"id":1,"name":"Uncategorized","slug":"uncategorized","banner":null,"status":1,"creator":{"id":1,"name":"demon slayer"},"moderator":1,"created_at":"2019-11-03 12:08:33","updated_at":"2019-11-04 11:11:01"},
note if with clause is removed in the query, I get:
"data":[{"id":1,"name":"Uncategorized","slug":"uncategorized","banner":null,"status":1,"creator":1,"moderator":1,"created_at":"2019-11-03 12:08:33","updated_at":"2019-11-04 11:11:01"},
in blade file, I what am doing is below code to print creator user name instead of their record id.
$record->creator->name
//or
$record->creator[0]->name
currently i get this:
Facade\Ignition\Exceptions\ViewException
Trying to get property 'name' of non-object (View: /Users/dragonar/Dev/pdp/resources/views/backend/category/index.blade.php)
Have you tried instead of
public function creator(){
return $this->hasOne(User::class, 'id', 'creator')->select('id', 'name');
}
This:
public function creator(){
return $this->hasOne(User::class, 'user_id')->select('id', 'name');
}
Or change the name of the function from creator() to user().
And then in your blade $record->user->name.
This what I found on the laravel docs:
Eloquent determines the foreign key of the relationship based on the model name. In this case, the Phone model is automatically assumed to have a user_id foreign key. If you wish to override this convention, you may pass a second argument to the hasOne method: return $this->hasOne('App\Phone', 'foreign_key');
Source
I ended up renaming creator to creator_id and in model, function is creator. like this now i can access desired data $record->creator->name or $record->creator->id.
what i wanted was instead of creator returing me id from original code, i could have been able to do $record->creator->name or $record->creator->id, directly.
I have 2 tables, one has different columns to record different users names based on authorisation level. but i would like to link to two together. at the moment i have tried the following:
User.php
public function approvals()
{
return $this->hasMany(Approval::class);
}
Approval.php
public function qs() {
return $this->belongsTo(User::class, 'id', 'qs');
}
index.blade.php
<td>{{ $approval->qs->name }}</td>
approvals db structure
Schema::create('approvals', function (Blueprint $table) {
$table->bigIncrements('id');
$table->unsignedBigInteger('project_id');
$table->integer('stage');
$table->unsignedBigInteger('qs')->nullable();
$table->unsignedBigInteger('pm')->nullable();
$table->unsignedBigInteger('rcm')->nullable();
$table->unsignedBigInteger('doc')->nullable();
$table->unsignedBigInteger('vpoc')->nullable();
$table->unsignedBigInteger('vpof')->nullable();
$table->timestamps();
});
users db structure
Schema::create('users', function (Blueprint $table) {
$table->bigIncrements('id');
$table->string('name');
$table->string('email', 100)->unique();
$table->timestamp('email_verified_at')->nullable();
$table->string('password');
$table->rememberToken();
$table->timestamps();
});
Am i going about this all wrong, the qs table column needs to be linking to the users.id?
It seems qs is the user id of the User model. So the relation to the Approval model is
public function qs()
{
return $this->belongsTo(User::class, 'qs');
}
And in User model
public function approvals()
{
return $this->hasMany(Approval::class, 'qs');
}
Now you can use
{{ $approval->qs->name }}
Eloquent determines the default foreign key name by examining the name of the relationship method and suffixing the method name with a _ followed by the name of the primary key column. However, if the foreign key on the Model is not parent_id, you may pass a custom key name as the second argument to the belongsTo method.
Laravel Documentation
If a parent model does not use id as its primary key, or you want to join the child model to a different column, you may pass a third argument to the belongsTo method:
public function qs() {
return $this->belongsTo(User::class, 'foreign_key_here_from_child_table', 'custom_column_from_parent_table');
}
Ok, so I have a basic messaging system, I have a relationship set up so I can just call $user->messages to retrieve an array of the users inbox messages. I also have a very simple show method that currently only grabs the message with id passed to the show() function.
The question is what would be the best way to protect that messages/2 URL so a user couldn't just type any number in the URL and access another users messages.
Should I use a route filter and basically run another query to ensure the message id is accessible by the user or is there something I can do with the relationship, perhaps check the id against the messages array and if it exists then the user must have access?
public function up()
{
Schema::create('messages', function(Blueprint $table) {
$table->increments('id');
$table->mediumText('subject');
$table->text('message');
$table->boolean('draft');
$table->integer('sender_id')->unsigned();
$table->softDeletes();
$table->timestamps();
$table->foreign('sender_id')->references('id')->on('users')->onUpdate('cascade');
});
Schema::create('message_assets', function(Blueprint $table) {
$table->increments('id');
$table->integer('message_id')->unsigned();
$table->string('filename', 255);
$table->softDeletes();
$table->foreign('message_id')->references('id')->on('messages')->onUpdate('cascade');
});
Schema::create('message_users', function(Blueprint $table) {
$table->increments('id');
$table->integer('message_id')->unsigned();
$table->integer('user_id')->unsigned();
$table->integer('read')->default(0);
$table->string('folder', 255)->nullable();
$table->softDeletes();
$table->foreign('message_id')->references('id')->on('messages')->onUpdate('cascade');
$table->foreign('user_id')->references('id')->on('users')->onUpdate('cascade');
});
}
in the simplest form, in your MessagesController in the show method, you can add an additional parameter to your query and get the record where message_id = the paramater from the url and the user_id on that message is the authenticated user's ID.... do something like
$message = App\Message::where('id', '=', $id)
->where('user_id', '=', Auth::user()->id)
->first();
if you are doing a more advanced design and have a MessageRepository, this logic could be extracted there so in your controller you do something like
$this->repository->getById($id);
and in the messages repository the getById() method would do something similar to the above code example using an eloquent model. this method allows the controller to be clean and the logic to be re-used elsewhere in the app if needed
ADDED to work with the pivot table specified above. this will only work for a user that has the message in the inbox:
DB::table('messages')
->join('message_users', 'messages.id', '=', 'message_users.message_id')
->where('message_users.message_id', $id)
->where('message_users.user_id', $userId)
->where('message_users.deleted_at', null)
->select('messages.*')
->first();