Laravel 5 - setting up relationships - laravel-5

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

Related

Eager load three way pivot table Laravel

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();

Laravel 5 Model Inheritance - returning parent data when getting child mode

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.)

Symfony 3 - Using ManyToOne and OneToMany and retrieving the data

I have a User table and a Job Title table in my database.
I have setup a User entity and a JobTitle entity.
In my User entity I have:
/**
* #ORM\ManyToOne(targetEntity="AppBundle\Entity\JobTitle", inversedBy="users")
* #ORM\JoinColumn(name="job_title_id", referencedColumnName="id")
*/
public $jobTitle;
In my JobTitle entity I have:
/**
* #ORM\OneToMany(targetEntity="AppBundle\Entity\User", mappedBy="jobTitle")
*/
private $users;
Here is an example setup in User Table:
job_title_id | Username
1 | jdoe
Here is an example setup in job Title Table:
id | job_title
1 | Owner
When I create the user everything is entered into the database as expected.
My issue is displaying the actual job title in my users list page.
Here is my Controller:
/**
* #Route("/users", name="users")
*/
public function listAction()
{
$loggedInUser = $this->getUser();
$users = $this->get('fos_user.user_manager')->findUsers();
if (!is_object($user) || !$user instanceof UserInterface) {
throw new AccessDeniedException('This user does not have access to this section.');
}
return $this->render('users/users.html.twig', array(
'user' => $loggedInUser,
'users' => $users,
));
}
In my twig template I was trying to use {{ user.jobTitle }}, but I get a conversion to string error which makes sense, but I have not idea how to get the actual job title name to display in the twig page.
If you need more info please let me know.
How do I retrieve the job title name using the job_title_id in the users table?
You haven't shown all your entities, so I'll have to 'guess' based on what I see above.
Looks like this: {{ user.jobTitle }} returns you a JobTitle object. You can confirm by doing a dump.
Since it's an object, I suspect you'll need to do something like this:
{{ user.jobTitle.job_title }}
Or possibly use the JobTitle's object methods (don't know what they are):
{{ user.jobTitle.getJobTitle }}
Or something like that. Try it out.

Usage of Eloquent objects as dictionary tables in Laravel

I have different classes that act as "dictionary" lists, for example, for CarType class I hava table with id, value fields (1, 'small'; 2, 'big' ..)
Is there a way I can use such model as a map list (with cached data in memory) to have sometthing like that in my blade files:
{{ CartType->value($model->type_id) }}.
The idea would be
to load in memory all the data (or at leas after 2 requests of
different values)
to be easily to wrap around some fields of the model
You can do this with Eloquent (using relationships, as #TimLewis suggests), but it if's a simple map with just a few values (ie. not hundreds of different options), you'll probably find that Eloquent adds a lot of overhead for such a simple function.
Instead, I'd use database queries inside custom accessors. For example,
Imagine you have a Car Eloquent model and each car has exactly one "car type" value (denoted by a car_type field in the database).
Imagine that you also have a table in your database for listing car types, called (not surprisingly) car_types.
Here's a quick diagram:
|------------------|
|cars | |------------------|
|------------------| |car_types |
|id INT PRIMARY_KEY| |------------------|
|car_type_id INT | -- one-to-many --> |id INT PRIMARY_KEY|
|... | |name CHAR |
|------------------| |------------------|
You could set up an Eloquent model for car_type and define a relationship. That would work just fine, but do you really need all the functionality of an Eloquent model for this?
Instead, you could set up an accessor on the cars model:
class Car extends Eloquent {
public function getCarTypeAttribute()
{
return DB::table('car_types')
->select('name')
->where('id', '=', $this->car_type_id);
}
}
This will perform better than a full Eloquent model plus relationships.
But you also asked about caching the list. You could easily expand the example above to store the full list in the cache instead of querying each time:
class Car extends Eloquent {
public function getCarTypeAttribute()
{
$list = Cache::get('car_types_list');
if (!$list) {
$list = DB::table('car_types')->select('name');
// save in the cache for 60 minutes
Cache::put('car_types_list', $list, 60);
}
// search the list for the type ID
foreach ($list as $type) {
if ($type->id===$this->id) {
return $type;
}
}
// the type wasn't found
throw new Exception("That's not right!");
}
}
Now, if you have a Car model, you can get the type like this:
$car = Car::find(1234);
$type = $car->CarType;

Counting a many to many relationship?

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

Resources