Yii2 ORM smart get related model - php

I have 3 Yii2 ActiveRecord models: Emplpoyee, Department and Organization.
Employee must have either one Department or one Organization, that is ensured by validation (fails if both department_id and organization_id are null or both are not null). Department must have one Organization, it's a standard yii2 ORM relation via hasOne().
Gii created this code for employee/organization relation:
class Employee
{
public function getOrganization()
{
return $this->hasOne(app\models\Organization::class, ['id' => 'organization_id']);
}
}
So when I call $employeeObject->organization, I will get null if organization_id in SQL table is null.
I want to modify this standard getter function to return $this->department->organization in be able to get $employee->organization via magic getter like this: if an employee has department - organization gets from department, otherwise - via standard relation.
Update: if I write:
/**
* #return ActiveQuery
*/
public function getOrganization()
{
if (!is_null($this->organization_id)) {
return $this->hasOne(app\models\Organization::class, ['id' => 'organization_id']);
} elseif (!is_null($this->department)) {
return $this->department->getOrganization();
} else {
// We should not be here, but what if we are?
}
}
How do I handle a situation of broken relation to Department or both organization_id and department_id is null? Which ActiveQuery do I return?

Relations definitions are used to build SQL query for related models. In some cases (eager loading, joins) query needs to be created before you get actual object, so you cannot use if ($this->organization_id) because $this is not actual model and $this->organization_id will alway be null. The only thing that you can get is to define regular relations without any conditions and getter method (getOrganizationModel()), which will return correct organization from relations:
class Employee extends ActiveRecord {
public function getDepartment() {
return $this->hasOne(Department::class, ['id' => 'department_id']);
}
public function getOrganization() {
return $this->hasOne(Organization::class, ['id' => 'organization_id']);
}
public function getOrganizationModel() {
if ($this->organization_id !== null) {
return $this->organization;
}
return $this->department->organization;
}
}
class Department extends ActiveRecord {
public function getOrganization() {
return $this->hasOne(Organization::class, ['id' => 'organization_id']);
}
}
Then you can query records with both relations and use getter to get organization:
$employees = Employe::find()->with(['department.organization', 'organization'])->all();
foreach ($employees as $employee) {
echo $employee->getOrganizationModel()->name;
}

Related

Laravel: Sorting a collection with a many to many relationship

I have two tables: assessments and benchmarks. benchmarks has a field called content. There is a many to many relationship between them: assessment_benchmark. I want to sort a collection of records from the assessment_benchmark table by the content attribute of the corresponding benchmark. I have tried:
$sorted = AssessmentBenchmark::all()->sortBy(function($assessmentBenchmark){
return $assessmentBenchmark->benchmark->content;
});
But this just does not work (it just returns the original order). However, when I return $assessmentBenchmark->comment for example, it does work (comment is a field in assessment_benchmark).
The models look like this:
class AssessmentBenchmark extends Model
{
public function benchmark()
{
return $this->belongsTo(Benchmark::class);
}
public function assessment()
{
return $this->belongsTo(Assessment::class);
}
}
class Benchmark extends Model
{
public function assessments()
{
return $this->belongsToMany(Assessment::class);
}
}
class Assessment extends Model
{
public function benchmarks()
{
return $this->belongsToMany(Benchmark::class);
}
}
Well, you can use below query for sorting, I'm gonna use Assessment model, because, I'm never use pivot modal before. Actually, I never had pivot model..
$assessments = Assessment::with(["benchmarks"=>function($query){
$query->orderBy("content","DESC");
}])
With method aşso provide you eagerloading, so when you put $assessments in iteration , you won't make new query for each relation
From chat discussion, it found that you have pivot field and for that you can change your belongsToMany relationship like this
class Benchmark extends Model
{
public function assessments()
{
return $this->belongsToMany(Assessment::class)->withPivot('comment','score')->withTimestamps();
}
}
class Assessment extends Model
{
public function benchmarks()
{
return $this->belongsToMany(Benchmark::class)->withPivot('comment','score')->withTimestamps();
}
}
Now fetch data
$assessment = Assessment::with(['benchmarks' => function($query){
$query->orderBy('content', 'desc');
}])->find($assessmentId);
In view you can render it like this
#foreach($assessment->benchmarks as $benchmark)
<tr>
<td>{{$benchmark->id}}</td>
<td>{{$benchmark->name}}</td>
<td>{{$benchmark->pivot->score}}</td>
<td>{{$benchmark->pivot->comment}}</td>
</tr>
#endforeach
For update you can use updateExistingPivot
For details check ManyToMany relationship https://laravel.com/docs/5.6/eloquent-relationships#many-to-many

Laravel models to implement one to many and many to many in single query

i have this table structure, project has one to many relation with rewards , rewards and shipping has many to many relation with pivot table reward_ship.
projects rewards shipping reward_ship
--------- -------- -------- ------------
id id id id
title amount location reward_id
amount project_id name ship_id
i am trying to extract one particular project details with all other associate tables data(rewards and shipping data using reward_ship table) in one query.
These is how i am trying
Projects Model
class Rewards extends Model {
public function projs(){
return $this->hasMany('App\Rewards');
}
public function rewds(){
return $this->belongsToMany('App\Shipping')
->withPivot('reward_ship', 'ship_id', 'reward_id');
}
public function shiplc(){
return $this->belongsToMany('App\Rewards')
->withPivot('reward_ship', 'ship_id', 'reward_id');
}
}
class Rewards extends Model {
public function proj() {
return $this->belongsTo('App\Projects');
}
}
Controller api class
Route::get('projects/{id}', function($id) {
$p = Projects::find($id);
$getd = Rewards::with('proj')
->where('rewards.project_id', '=', $p->id)
->get();
});
it doesn't work.
i search and tried many related model base query in larvel.
i know my implementation are wrong. Please suggest me to work out.
You can use Laravel 5.5 new feature API Resources.
It helps you to format the output of objects such as models or collections, to display attributes and also relationships.
So, you could do something like this in your ItemResource:
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\Resource;
class Project extends Resource
{
/**
* Transform the resource into an array.
*
* #param \Illuminate\Http\Request
* #return array
*/
public function toArray($request)
{
return [
'project_id' => $this->project_id,
'title' => $this->title,
'amount' => $this->amount,
// To access relationship attributes:
'rewards' => $this->rewards->load('shippings'),
];
}
}
Then in your controller, you just need to create a new Resource instance and pass the item object that you want to return:
use App\Http\Resources\Project as ProjectResource;
// some code
/**
* Show a single formatted resource.
*
* #param Project $project
* #return ProjectResource
*/
public function show($project)
{
return new ProjectResource($project);
}
// the rest of your code
The output should be the expected.
You have to fix the relationships that you have :
Projects Model :
public function rewards(){
return $this->hasMany('App\Rewards');
}
Rewards Model :
public function projects() {
return $this->belongsTo('App\Projects');
}
public function shippings(){
return $this->belongsToMany('App\Shipping','reward_ship', 'reward_id', 'ship_id');
}
Shipping model:
public function rewards(){
return $this->belongsToMany('App\Rewards','reward_ship', 'ship_id', 'reward_id');
}
After that you can call the relationships in the controller to eager load the wanted elements like this :
$project = Projects::with('rewards.shippings')
->where('id', $project_id)
->get();
And in the view you can loop over the rewards then get the shippings like this :
#foreach ($project->rewards as $reward)
<p>This is a reword {{ $reward->amount }}</p>
#foreach ($reward->shippings as $shipping)
<p>This is a shipping {{ $shipping->name }}</p>
#endforeach
#endforeach
class Project extends Model
{
public function rewds()
{
return $this->hasMany('App\Rewards');
}
public function shiplc()
{
return $this->hasManyThrough('App\Shipping', 'App\Rewards');
}
}
class Rewards extends Model
{
public function shiplc()
{
return $this->belongsToMany('App\Shipping');
}
public function projs()
{
return $this->belongsTo('App\Project');
}
}
class Shipping extends Model
{
public function shiplc()
{
return $this->belongsToMany('App\Shipping');
}
}
Route::get('projects/{id}', function($id) {
$p = Projects::with(['rewds', 'shiplc'])->find($id);
});
Project.php
class Project extends Model {
public function rewards() {
return this->hasMany(Reward::class, 'project_id', 'id');
}
}
Reward.php
class Reward extends Shipping {
public function shipping(){
return $this->belongsToMany(Shipping::class, 'reward_ship', 'reward_id', 'ship_id');
}
public function project(){
return $this->belongsTo(Project::class);
}
}
You can retrieve it like this:
$projectDetails = Project::where('id', $projectId)
->with(['rewards', 'rewards.shipping'])->get();

How to apply relationship with two id's in laravel

So I have a model called data_storage and another model entity_states
I have to fetch the record from data_storage with entity_states where entity_state has data_storage_id and state_id.
How can I use eloquent to achieve this ?.
Or Ill have to use Query builder and use innerJoin?
Update1
My Actual Query
$this->values['new_leads'] = $data_storages->with('actions','states','sla')->where('wf_id',$wfid)->get();
My data_storage modal
class data_storages extends Model
{
//
protected $fillable = ['layout_id','member_id','company_id','team_id','data','status','wf_id'];
function actions()
{
return $this->hasMany('App\Models\ActionDataMaps', 'data_id', 'id' );
}
function states()
{
return $this->hasOne('App\Models\workflow_states','id','status');
}
function sla()
{
//Here I have to get those row from entity_states model where , data_storage_id and state_id
}
}
Thanks
Here's the more reasonable way to do it:
class DataStorage extends Model {
public states() {
return $this->belongsToMany(State::class,"entity_states");
}
}
class State extends Model {
public storages() {
return $this->belongsToMany(DataStorage::class,"entity_states");
}
}
Then you can eager-load related models via e.g.:
$storage = DataStorage::with("states")->first();
$storage->states->first()->column_in_related_state;
Or via the state:
$state = State::with("storages")->first();
$state->storages->first()->column_in_related_storage;
If there are additional columns in the pivot table entity_states then you can refer to them in the relationship as e.g.:
public states() {
return $this->belongsToMany(State::class)->withPivot("pivot_column");
}
In your model data_storage you can define a property / method entity_states to get them:
class data_storage extends Model
{
public function entity_states()
{
return $this->hasMany('App\entity_states','data_storage_id')->where('state_id ','=',$this->table());
}
}
Then you can access them in an instance by
$entityStatesOfDataStorage = $yourDataStorageInstance->entity_states;
See this link:
https://laravel.com/docs/5.3/eloquent-relationships
for Query Builder you may use this:
DB::table('data_storage')
->join('entity_states','data_storage.data_storage_id','=','entity_states.state_id')
->get();
For your reference Laravel Query Builder

Get laravel data with relation between tables

I have the following data models:
class Cliente extends Model
{
public function sector()
{
return $this->belongsTo(Sector::class,'sectoresId');
}
}
class Sector extends Model
{
public function sectorLanguage()
{
return $this->hasMany(SectorLanguage::class,'sectoresId');
}
public function cliente()
{
return $this->hasMany(ClienteLanguage::class,'sectoresId');
}
}
class SectorLanguage extends Model
{
public function sector()
{
return $this->belongsTo(Sector::class,'sectoresId');
}
public function idioma()
{
return $this->belongsTo(Idioma::class,'idiomasId');
}
}
I want to recover all the active clients and the name of the sector to which it belongs, if I do something like this
$cliente = Cliente::where('active','1');
When I run $client I can not enter the attribute
foreach($cliente as $cli) {
$cli->sector->sectorLanguage->nombre;
}
Why? It only works for me when I find it by id
$cliente = Cliente::find(1);
echo $cliente->sector->sectorLanguage->nombre;
How can I get what I need without resorting to doing SQL with Query Builder.
Thank you very much, greetings.
According to your defined relations, the Sector has many sectorLanguage, it means you're receiving a collection, so you should work on an object like this:
$cliente->where('active', 1)
->first()
->sector
->sectorLanguage
->first()
->nombre;
Cliente::where('active', 1) and sectorLanguage gives you the collection, so you
should first get the first item from $cliente & sectorLanguage and then apply the
desired relationship
Hope it should work!

laravel 4 Cannot Retrieve ALL results ? pivot table Many to Many

I am a bit stuck on this...
I have 3 tables: photographers, languages and languages_spoken (intermediate table).
I am trying to retrieve all the languages spoken by a photographer. I defined my models like this:
class Photographer extends Eloquent {
/**
* Defining the many to many relationship with language spoken
*
*/
public function languages() {
return $this->belongsToMany('Language', 'languages_spoken', 'language_id', 'photographer_id');
}
class Language extends Eloquent {
/**
* Defining the many to many relationship with language spoken
*
*/
public function photographers() {
return $this->belongsToMany('Photographer', 'languages_spoken', 'language_id', 'photographer_id')
->withPivot('speakslanguages');
}
This is how I was trying to retrieve all the results for the logged in photographer:
$photographer = Photographer::where('user_id', '=', $user->id);
if ($photographer->count()) {
$photographer = $photographer->first();
// TEST
$spokenlang = $photographer->languages;
die($spokenlang);
// END TEST
} else {
return App::abort(404);
}
The problem is that in my db I have 4 entries for the same photographer. but when I do this I only get the last result...
[{"id":"3","language_name":"Afrikaans","updated_at":"-0001-11-30 00:00:00","created_at":"-0001-11-30 00:00:00","native_name":"Afrikaans","ISO639_1":"af","pivot":{"language_id":"3","photographer_id":"3"}}]
Any idea on what is wrong ?
Thanks a lot for your help!!!
The third parameter to belongsToMany should be the foreign key.
In the Photographer class:
public function languages() {
return $this->belongsToMany('Language', 'languages_spoken', 'language_id', 'photographer_id');
}
...should be:
public function languages() {
return $this->belongsToMany('Language', 'languages_spoken', 'photographer_id');
}
In the Language class:
public function photographers() {
return $this->belongsToMany('Photographer', 'languages_spoken', 'language_id', 'photographer_id')
->withPivot('speakslanguages');
}
Should be:
public function photographers() {
return $this->belongsToMany('Photographer', 'languages_spoken', 'language_id')
->withPivot('column1', 'column2', 'column3'); // withPivot() takes a list of columns from the pivot table, in this case languages_spoken
}
But, since you're not even using strange keys, you don't need to pass that third parameter at all.
So this is just fine:
public function languages() {
return $this->belongsToMany('Language', 'languages_spoken');
}
And:
public function photographers() {
return $this->belongsToMany('Photographer', 'languages_spoken')
->withPivot('column1', 'column2', 'column3'); // withPivot() takes a list of columns from the pivot table, in this case languages_spoken
}

Categories