I've created my first composer package for Laravel 8. It provides a user group model and some routes. A couple of them are
POST | api/v1/usergroups | usergroups.create | ITGuy1990\Usergroups\Controllers\UsergroupController#create | api
GET|HEAD | api/v1/usergroups | usergroups.index | ITGuy1990\Usergroups\Controllers\UsergroupController#index | api
GET|HEAD | api/v1/usergroups/{usergroup} | usergroups.show | ITGuy1990\Usergroups\Controllers\UsergroupController#show | api
PATCH | api/v1/usergroups/{usergroup} | usergroups.update | ITGuy1990\Usergroups\Controllers\UsergroupController#update | api
DELETE | api/v1/usergroups/{usergroup} | usergroups.destroy | ITGuy1990\Usergroups\Controllers\UsergroupController#destroy | api
All working fine so far. But now the hard part. Outside of the package, and in my Laravel application I am going to add another eloquent model. A TodoList model. Now, i want to make a "one to many" relationship between a UserGroup model and TodoList model. I want the api/v1/usergroups/{usergroup} route to also return the TodoList models associated to the UserGroup model. I figured that I probable want to add a query parameter to select relationships to include like so: http://localhost:8000/api/v1/usergroups/1?include=todolists
But how can I make the usergroups.show route include relationships that are not part of the usergroups package. I could, but do not want to include the todolist model in the usergroups package. Because I want the usergroups package to only be responsible for usergroups, right?
Thank you for helping me in advance.
ITGuy1990
If I understand correctly you are new to Laravel. If you want to show the todolist connected to each usergroup on the usergroups.show route, you can do it by making a model relations. If each group will have more than 1 todolist and each todolist will belong to more than 1 group, this is a many to many relation:
In UserGroup model:
public function todolist()
{
return $this->hasMany(ToDoList::class);
}
In ToDoList model:
public function usergroups()
{
return $this->belongsToMany(UserGroup::class);
}
In Controller:
$usergroups = UserGroup::with('todolist')->get();
In blade:
#foreach($usergroups->todolist as $todolist)
{{$todolist->name}}
#endforeach
Related
I've 2 tables (students and accounts).
students
+----+-----+------+
| id | nik | name |
+----+-----+------+
accounts
+----+-----+----------+----------+
| id | nik | username | password |
+----+-----+----------+----------+
When I want to get the account data from the relationship $student->account->username is running perfectly. But, when i try $account->student->name, i get an error :
Trying to get property of non-object
I've check the data already and All were existed. What should I Do?
public function account()
{
return $this->hasOne(Account::class, 'nik', 'nik');
}
public function student()
{
return $this->belongsTo(Student::class, 'nik', 'nik');
}
Change the method belongsTo() in the model Account to:
hasOne(Student::class, 'nik', 'nik')
I am struggling to get eager loading working in Laravel when working with a three way pivot table. The database is set up as follows:
+-------+--------+-------+-------------+
| deals | venues | users | redeems |
+-------+--------+-------+-------------+
| id | id | id | deal_id |
| title | name | name | user_id |
| | | | venue_id |
| | | | redeemed_at |
+-------+--------+-------+-------------+
Where redeems is obviously the pivot table. The user model look like this:
<?php
class User extends Authenticatable
{
/**
* The deals redeemed by the user.
*/
public function deals()
{
return $this->belongsToMany(Deal::class, 'redeems')
->withPivot(['venue_id', 'redeemed_at']);
}
}
This allows me to do this:
$users = Users::with('deals')->all();
And then I get a nice overview of the user and all the deals that they have redeemed in a structure like this:
{
"id": 1,
"name": "Diederik",
"deals": [
{
"id": 33,
"title": "Deal Title",
"pivot": {
"user_id": 1,
"deal_id": 33,
"venue_id": 50
}
}
]
}
Now I would like to add the venue information by eager loading it instead of querying the $user->pivot->venue_id afterwards.
I tried setting up a belongsTo relationship in the deal model and then doing a nested with like so: $users = Users::with('deals.venue')->all(); but that does not work because the deal table does not have a foreign key to a venue. This is because a deal can be applied to multiple venues.
I am aware of the fact that this is very easily achievable using normal database queries, but for the way our API works it is essential to use the eloquent with relation function as our URL setup looks for includes that way.
TLDR: How to eager load nested three way pivot table relations.
I would sugest you to treat the pivot table as another model (Redeem). Then make one-to-many relationships with the others tables/models (Deal/Venue/User) so you could just make your query using Eloquent like this to lazy load the objects you need:
$redeems = User::find($id)->redeems;
$redeems->load(['venue','deal']);
return $redeems;
To format the data/response in an exact way you can use API Resources if your using 5.5. Or Fractal for 5.4 or below.
If I'm understanding your question correctly, you have a one-to-many relationship between deals and venues where you want to get a specific venue. Normally, you would create a hasMany() or belongsToMany() relationship in your deals model, then if you want to filter the venues further, you would write an Eloquent subquery like so:
// Gets first venue of deals
Users::with('deals', 'deals.venues' => function ($query) {
$query->first();
})->all();
So I have this crazy idea that has to do with Laravel and model inheritance. I would like to configure a set of models with a single parent but when I request the child model I would like the data returned. For example I would have a Contacts model which is the parent:
Contacts: id, first_name, last_name, image
Then I would have a series of contact types that inherit from Contacts. Each of these child models would have their own set of fields (i.e. for members I need to know when they joined,etc. but for volunteers I might need to know if they have an up-to-date first-aid certificate). Here are a few examples:
Members: contact_id, joined_on, birthday, medical_concerns
Volunteers: contact_id, current_first_aid, interests
Staff: contact_id, pay_rate
I would love to be able to do something like:
$members = \App\Member::all();
and have the contact AND member data returned as if everything was one row, like this:
+---+------------+-----------+-------+------------+------------+------------------+
|id | first_name | last_name | image | joined_on | birthday | medical_concerns |
+---+------------+-----------+-------+------------+------------+------------------+
| 1 | Fred | Bloggs | null | 2015-01-01 | 1993-10-22 | false |
| 2 | Jim | Phillips | null | 2016-04-30 | 1987-09-22 | true |
+---+------------+-----------+-------+------------+------------+------------------+
And to make it a little more difficult I would like all of the relationships that apply to the parent to work for the child. So I could do something like this:
$members = \App\Member::find(1)->phone
And, even though the Member model doesn't have a relationship defined to the Phone model it would return the phone related to the Contact because the parent has that relationship.
I would also like to be able to specify columns that don't belong to the child when retrieving data and not have Laravel throw an error:
$members = \App\Member::all(['first_name','last_name','joined_on'])
I have messed around with overriding the Eloquent model and writing my own version of the all and find methods which is working but it looks like I may have to override all of the methods to get this to work and maybe that would be more work than just forgoing Eloquent and looking for other (or my own bespoke) solution.
So I guess my questions is: Is there an "easy" way to do this with Laravel or am I trying to make it do things that it was never intended to do?
I think you can do like this:
$members = \App\Members::with('contact')->all();
Of course your Members model should have defined the belongsTo relation to the contact model.
Is there an "easy" way to do this with Laravel or am I trying to make it do things that it was never intended to do?
Yes.
Eloquent doen't manage inheritance in this way. It might be better to implement polymorphic relations instead.
However, overriding just this bit seems to serve your purposes:
abstract class ContactSubclass extends Contact
{
protected $with = 'parent';
public function parent()
{
return $this->belongsTo(Contact::class, 'contact_id');
}
public function newQuery()
{
$contactTable = (new Contact())->getTable();
$thisTable = (new static())->getTable();
$builder = parent::newQuery();
$builder->join($contactTable, "$thisTable.contact_id", '=', "$contactTable.id");
return $builder;
}
public function newFromBuilder($attributes = [], $connection = null)
{
$model = parent::newFromBuilder($attributes, $connection);
if ($model->parent) {
$model->setRawAttributes(array_merge($model->parent->getAttributes(), $model->getAttributes()), true);
}
return $model;
}
protected function getRelationshipFromMethod($method)
{
if (method_exists(parent::class, $method)) {
return $this->parent->getRelationshipFromMethod($method);
}
return parent::getRelationshipFromMethod($method);
}
}
Have Member and other subclasses extend this class (or add these overrides in each class that extends Contact).
(I haven't tested it thoroughly, give it a try. Also, this won't handle eager loads directly; try to find what to override if you want to support that.)
I have been struggling a little with my application and asked a lot of questions, and before I go any further, I just want to make sure that my relationships are ok.
So I am creating an application whereby you can generate different documents based on the input you provide. So, I create a project, and within that project there is a select field which has different types of documents. If I select DocumentA for example, the create form for DocumentA will be displayed to the user. If I choose DocumentB, the create form for DocumentB will be displayed. Now although these forms are different and take different inputs, I wanted a way whereby I would not have to create a new table for every document type. So I came up with the following.
So within a project, I select I want to create DocumentA. I am then displayed the view documentA.create. This view has hidden inputs for the document name and description. The form fields labels are the key in the document_data table, and the value is the input for this field. So, if I create DocumentA, my database might look like this
project
id | projectName |
------------------
1 | Project One |
------------------
document
id | projectId | documentName |
--------------------------------
1 | 1 | DocumentA |
--------------------------------
document_data
id | documentId | key | value |
----------------------------------------------
1 | 1 | clientName | Google |
----------------------------------------------
2 | 1 | projectName | Analytics |
----------------------------------------------
3 | 1 | Contact | Mr Sharp |
----------------------------------------------
4 | 1 | startDate | 29/12/2016 |
----------------------------------------------
Where I am struggling at the moment is that the foreign key documentId is in the document_data table. However, things are only working if I set a foreign key in both of my Models classes e.g.
class Document extends Model
{
protected $table = 'documents';
protected $guarded = [];
public function documentData()
{
return $this->hasMany('App\DocumentData', 'documentId');
}
}
class DocumentData extends Model
{
protected $table = 'document_data';
protected $guarded = [];
public function document()
{
return $this->belongsTo('App\Document', 'documentId');
}
}
If I dont set it in both classes, I get a MethodNotAllowedHTTP exception with no information about it. I have been able to create documents without problem, the problem comes when I need to update. The edit page for DocumentA has a form starting like this
{{ $document }}
{!! Form::model($project->document, [
'class'=>'form-horizontal',
'method' => 'PATCH',
'route' => ['projects.documents.update', $project, $project->document]
]) !!}
Now when I output $document above, I get the correct document that I am working on, as I should do. However, in the update function, if I output $document on its own or if I do
public function update(Request $request, Project $project, Document $document)
{
dd($project->document);
return null;
}
I see both DocumentA and DocumentB. Shouldnt the update only be passed DocumentA?
Why would this be? Any information or advice hugely appreciated.
Many thanks
I think the issue is the relationship between project and document. You probably have document belongsTo project, and project hasMany document, as you can have many documents with projectId = 1. Isn't it?
It what I said is okay, when you write $project->document, it brings you all the documents that belong to that project. It's confusing because you named the relationship 'document' instead of 'documents'.
You can do 2 things:
1- If each project can have only 1 document, change the relationship in the 'Project' model to hasOne Document. Then, if you do $project->document it will bring you only one.
2- If your app allows a project to have multiple documents, leave the relationship as hasMany (I'd recommend to rename it to documents instead of document), and pass the $document object to the update form, instead of passing the $project and trying to access the document from there.
Please let me know if I missed the point of your question and I'm totally wrong
I have a many to many relationship between users and images.
User Model
public function image()
{
return $this->belongsToMany('\App\Image');
}
Image Model
public function user()
{
return $this->belongsToMany('\App\User');
}
Tables
users
id | name
images
id | url
image_user
id | image_id | user_id
When a user 'favourites' an image, it's stored in the pivot table.
id | image_id | user_id
1 1 1
2 2 1
3 1 2
I need a count of each images favourites.
I try something like:
Image::with('user')->find(1)->count();
But this counts the number of users, not the number of favourites.
Ideally I would like to return all of the image data along with a count of the user data - how can I do this?
You can do this:
$image = Image::with('user')->find(1) // Get the image with user eager loading
$image->name; // Access any attribute
$image->users->count(); // Get the user count
You can even add a few lines in your Image model to create a "custom" attribute:
public function getFavoritesAttribute()
{
return count($this->users);
}
And then use it like this:
$image->favourites;
There is a more detailed solution here:
Laravel many to many loading related models with count