Dynamic hasMany relationship in Laravel/Eloquent - php

I want to have a dynamic relationship based on a class name, which is stored as a property on a far related model.
There are lots of different class names so I don't want to make lots of relationships. It's a bit complicated but it has to be this way.
function subjectvotes() {
$className = $this->masterCourse->subject->class_name;
return $this->hasMany($class)->orderBy('value');
}
Trying to do it this way won't work mid-query because it doesn't always have the 'child' model information yet (as ->get() hasn't been run). Another issue with this is it's running the same $this->parent->grandparent query every time — not ideal.
Before I start the relationship query, I can run this to get the class name and table name respectively:
$className = $masterCourseModel->subject->class_name;
$tableName = ( new $className )->getTable();
But how to feed this into the relationship I'm not yet sure:
$masterCourseModel->studentCourse()->with('subjectvotes')->get();
Without creating a lot of relationships for each possible class, how do I feed the $className in to create a dynamic hasMany relationship, or can I do this by adding a new query scope and feeding in the $tableName or $className that way?
Thanks in advance for any assistance.

Related

Different implementation of Eloquent instance for the same table

I'm currently working a tournament organization project, I would like to what's the best technique to have different implementation for the same model, for example I have model called matches, than retrieves data related to matches, but these matches may have different types depending on match_type field specified in matches model, what I did is creating parent class /Match , and having different types of matches extend this parent class. Ex:
Class SinglElimination/Match extends /Match{}
Class GroupStage/Match extends /Match{}
so with this design I have to get the parent match first to get the match_type then re-run the query to get a match with the needed child model
$match=Match::find($id);
$type = $match->match_type;
$childMatch = new $match_type.'/Match'();
$match = $match->where('_id', $this->_id)->first();
which I know is nowhere near clean, so how would you implement this ?
If I were you I would separate those 3 classes and exclude any extending. Take a look at Polymorphic relationships in Laravel, here is the quick link. It will be a cleaner approach and in my opinion it would be the best approach here, all you'll have to do is design the tables properly and do relationships properly too.

Connecting database fields and model attributes in Laravel

I'm just getting going with Laravel, and have used Eloquent to define my Campaign table. I have a Campaign model which is currently empty.
I'm not sure how to add attributes to this model to represent the fields in the db - or even if I should. The Laravel documentation seems thin on models and searches keep leading me to accessors and mutators.
If I have a database field called platform_type in my campaigns table, how do I link the PlatformType model attribute to this field?
To clarify:
This is not a question about relationships - there is only one entity in my solution thus far.
platform_type is a field in my campaigns table because it is an attribute of a campaign - I'm asking how to represent this in my model.
The model has an internal array which stores the attributes of a given row (it's called $attributes and replicated by $original if you look for them in the source code). The reason it's replicated is so when you call save() it will only do a save if you actually changed them from the originals.
You can access said attributes via $modelInstance->getAttribute("platform_type") or $modelInstance->platform_type which will call the magic __get method that in turn calls the getAttribute
So in your case you can have:
$campaign = Campaign::find($id);
echo $campaign->platform_type;
The ORM will automatically create the relevant SQL query and fill the model instance with the attributes of the row it finds.
You need to define relationships. In the PlatformType model:
public function campaigns()
{
return $this->hasMany(Campaign::class, 'platform_type');
}
And in the Campaign model:
public function platformType()
{
return $this->belongsTo(PlatformType::class, 'platform_type');
}
You also need to rename the campaign table to campaigns. Or you should add this to the model to be able to use a custom name:
protected $table = 'campaign';
At this point, these tables will be connected and relationships will work. However, it is recommended to add foreign key constraints.

laravel Eloquent join and Object-relationship mapping

Ok so i'm kind of newish to eloquent and laravel (not frameworks tho) but i hit a wall here.
I need to perform some queries with conditions on different tables, so the eager load (::with()) is useless as it creates multiples queries.
Fine, let use the join. But in that case, it seems that Laravel/Eloquent just drops the concept of Object-relationship and just return a flat row.
By exemple:
if i set something like
$allInvoicesQuery = Invoice::join('contacts', 'contacts.id', '=', 'invoices.contact_id')->get();
and then looping such as
foreach ($allInvoicesQuery as $oneInvoice) {
... working with fields
}
There is no more concept of $oneInvoice->invoiceFieldName and $oneInvoice->contact->contactFieldName
I have to get the contacts fields directly by $oneInvoice->contactFieldName
On top of that the same named columns will be overwrited (such as id or created_at).
So my questions are:
Am i right assuming there is no solution to this and i must define manually the field in a select to avoid the same name overwritting like
Invoice::select('invoices.created_at as invoice.create, contacts.created_at as contact_create)
In case of multiple joins, it makes the all query building process long and complex. But mainly, it just ruins all the Model relationship work that a framework should brings no?
Is there any more Model relationship oriented solution to work with laravel or within the Eloquent ORM?
Instead of performing this join, you can use Eloquent's relationships in order to achieve this.
In your Invoice model it would be:
public function contact(){
return $this->belongsTo('\App\Contact');
}
And then of course inside of your Contact model:
public function invoices(){
return $this->hasMany('\App\Invoice');
}
If you want to make sure all queries always have these active, then you'd want the following in your models:
protected $with = ['Invoice']
protected $with = ['Contact'];
Finally, with our relationships well defined, we can do the following:
$invoices = Invoice::all();
And then you can do:
foreach($invoices as $invoice)[
$invoice->contact->name;
$invoice->contact->phone;
//etc
}
Which is what I believe you are looking for.
Furthermore, you can find all this and much more in The Eloquent ORM Guide on Laravel's site.
Maybe a bit old, but I've been in the same situation before.
At least in Laravel 5.2 (and up, presumably), the Eloquent relationships that you have defined should still exist. The objects that are returned should be Invoice objects in your case, you could check by dd($allInvoiceQuery); and see what the objects are in the collection. If they are Invoice objects (and you haven't done ->toArray() or something), you can treat them as such.
To force only having the properties in those objects that are related to the Invoice object you can select them with a wildcard: $allInvoicesQuery = Invoice::select('invoices.*')->join('contacts', 'contacts.id', '=', 'invoices.contact_id')->get();, assuming your corresponding table is called invoices.
Hope this helps.

get *related* linked models in laravel eloquent instead of raw SQL

I'm trying to get 'related' linked models by querying a link table, named company_projects which holds (as you expect) the id's of companies and projects (projects are kind of product-categories).
In this case, the used flow to determine a related project is:
Get companies who are in the same project ('product category') as you
Find the other project id's which are linked to those companies
Get the info of the linked projects fetched by last step
What i'm trying to do is already functional in the following raw query:
SELECT
*
FROM
projects
WHERE
projects.id IN
(
SELECT cp1.project_id
FROM company_projects cp1
WHERE cp1.company_id IN
(
SELECT cp1.company_id
FROM projects p
LEFT JOIN company_projects cp2 ON cp2.project_id = p.id
WHERE p.id = X AND cp2.company_id != Y
)
)
AND projects.id != X
X = ID of current project ('product category')
Y = ID of current 'user' (company)
But my real question is, how to do this elegantly in Laravel Eloquent (currently v4.2). I tried it, but I have no luck so far...
Update:
I should note that I do have experience using Eloquent and Models through multiple projects, but for some reason I just fail with this specific query. So was hoping to see an explained solution. It is a possibility that I'm thinking in the wrong way and that the answer is relatively easy.
You will need to utilize Eloquent relationships in order to achieve this. (Note that I am linking to the 4.2 docs as that is what you are using, but I would highly suggest upgrading Laravel to 5.1)
I am assuming you have a 'Company' and 'Project' model already. Inside each of those models, you need to a define a method that references its relationship to the other model. Based on your description, it sounds like the two have a Many to Many relationship, meaning that a company can have many projects and a project can also belong to many companies. You already have a database table linking the two. In the Eloquent ORM this linking table is called a pivot table. When you define your relationships in the model, you will need to pass the name of that pivot table as your second argument. Here's how it could look for you.
Company model:
class Company extends Model
{
/**
* Get the projects attached to a Comapny. Many To Many relationship.
*/
public function projects()
{
return $this->belongsToMany('Project','company_projects');
}
}
Project model:
class Project extends Model
{
/**
* Get the companies this project belongs to. Many To Many relationship.
*/
public function companies()
{
return $this->belongsToMany('Company','company_projects');
}
}
If your models have these relationships defined, then you can easily reference them in your views and elsewhere. For example, if you wanted to find all of the projects that belong to a company with an ID of 1234, you could do this:
$company = Company::find(1234);
$projects = $company->projects;
Even better, you can utilize something called eager loading, which will reduce your data lookup to a single line (this is mainly useful when passing data to views and you will be looping over related models in the view). So those statements above could be rewritten as:
$company = Company::with('projects')->find(123);
This will return your Company model with all its related products as a property. Note that eager loading can even be nested with a dot notation. This means that you can find all the models that link to your main model, and then all the models for those models, and so on and so forth.
With all of this in mind, let's look at what you specifically want to accomplish.
Let us assume that this method occurs in a Controller that is being passed a project id from the route.
public function showCompaniesAndProjects($id)
{
//Get all companies in the same project as you
//Use eager loading to grab the projects of all THOSE companies
//Result will be a Project Object with all Companies
//(and those projects) as Eloquent Collection
$companies = Project::with('companies.projects')->find($id);
//do something with that data, maybe pass it to a view
return view('companiesView')->with('companies',$companies);
}
After defining your relations in your models, you can accomplish that whole query in a single line.

How should I structure the Eloquent ORM models for multiple intermediate tables in Laravel?

I can't find anywhere the information on how you have several intermediate tables with your Eloquent ORM models. The problem I'm facing is that I have a table for my users, permissions and roles. These are the 4 tables:
Permissions:
id
name
Permission_roles:
id
name
Permission_role_mappings:
id
permission_id
permission_role_id
Permission_role_user_mappings:
id
permission_role_id
user_id
(Well, I also have a users table but the layout of it doesn't matter since the foreign key is in permission_role_user_mapping.)
So the problem is that I want to be able to get the data from the permissions table when calling from the User model. I have some trouble grasping the workflow with Eloquent ORM altogether so if I'm missing something basic which is crucial then please point it out.
According to the documentation it seems that I don't need to create models for the intermediate tables. So how would I specify the relationship from the User class? Could I do something similar to this?
class User extends Eloquent {
public function permission_role()
{
return $this->has_many_and_belongs_to('Permission_Role', 'permission_role_user_mappings');
}
public function permission()
{
return $this->has_many_and_belongs_to('Permission_Role', 'permission_role_user_mappings')->has_many_and_belongs_to('Permission','permission_role_mappings');
}
}
This doesn't seem to be working, this is the error:
User::find(1)->first()->permission()->first();
...
Method [permission] is not defined on the Query class.
I also want to be able to get data by starting from Permission_Role and Permission. I'd prefer that the answer would help me specifying all the models required.
Eloquent relationships are accessed as an object property instead of a function.
User::find(1)->first()->permission;
You can wrap that above statement in the dd function to get a look at it.
This guide on Eloquent Relationships should be helpful
Edit for question in comments about selecting all permissions in the role:
$roles = array();
$permission_roles = User::find(1)->permission_roles()->get();
foreach ($permission_roles as $pr) {
if (! in_array($pr->permissions)) {
$roles[] = $pr->permissions;
}
}
This will get you what you want. However, this will end up doing a lot of queries. It's best to make use of Eager Loading here.

Categories