I'm building a small application on Laravel 5.4and facing a little difficulty in syncing the pivot table in many to many relationship, I went through this link and not understood properly,
I'm having a pivot table which is mandatory field(I mean it will have field). I'm having a relationship something like this:
class Interaction extends Model
{
public function clientsAssociation()
{
return $this->belongsToMany('App\Contact', 'contact_client_interaction', 'interaction_id', 'contact_id')->withPivot('company_id')->withTimestamps();
}
}
I'm getting an array with set of values related to sync to these models. I'm confused how to place pivot data and while updating:
foreach ($data['clientParticipants'] as $clientParticipant)
{
if(array_key_exists('company_id', $clientParticipant))
{
$contact[] = Contact::find(json_encode($clientParticipant['value']));
$pivotData = ['company_id' => $clientParticipant['company_id']];
}
else
{
$contact[] = Contact::find(json_encode($clientParticipant['value']));
$pivotData = ['company_id' => Contact::find(json_encode($clientParticipant['value']))->company()->withPivot('created_at')->orderBy('pivot_created_at', 'desc')->first()->id])];
}
$interaction->clientsAssociation()->sync($contact);
}
Guide me to achieve this. Thanks
Well I did something like this:
foreach ($data['clientParticipants'] as $clientParticipant)
{
if(array_key_exists('company_id', $clientParticipant))
{
$pivotData = ['company_id' => $clientParticipant['company_id']];
$syncData = Contact::find(json_encode($clientParticipant['value']))->id;
$contact[$syncData] = $pivotData;
}
else
{
$value = Contact::find(json_encode($clientParticipant['value']));
$syncData = $value->id;
$pivotData = ['company_id' => $value->company()->withPivot('created_at')->orderBy('pivot_created_at', 'desc')->first()->id];
$contact[$syncData] = $pivotData;
}
}
$interaction->clientsAssociation()->sync($contact);
And it works. So basically idea is to push an array with key of pivot elements and it will execute properly.
Related
I have two tables to handle the parents of an animal, in the pivot table I store the "id" of the child animal and the parent animal, in the other table I have a gender field to define whether it is the mother or the father.
The problem is when updating this data, because is a many-to-many relationship I can only call all the parents, what I want is to update one by one comparing them with the gender.
In my "Ganado" model I have two functions defined to see the parents and to assign them:
public function padres()
{
return $this->belongsToMany('App\Ganado', 'hijos_padres', 'id_hijo', 'id_padre');
}
public function hijos()
{
return $this->belongsToMany('App\Ganado', 'hijos_padres', 'id_padre', 'id_hijo');
}
public function asignarPadre($id_padre)
{
$this->padres()->sync($id_padre, false);
}
In my controller I update the data like this:
public function update(Request $request, $id)
{
$bovino = Ganado::findOrFail($id);
$bovino->codigo_ganado = $request->get('codbovino');
$bovino->nombre = request('nombre');
$bovino->lote_ingreso = request('loteIn');
$bovino->lote_actual = request('loteAct');
$bovino->ganaderia = $request->get('ganaderia');
$bovino->etapa = $request->get('etapa');
$bovino->genero = $request->get('genero');
$bovino->id_raza = $request->get('raza');
$bovino->fecha = Carbon::createFromFormat('d/m/Y', $request->input('fechaNac'))->format('Y-m-d');
if ($request->hasFile('imagen')) {
$file = $request->imagen;
$file->move(public_path() . '/images', $file->getClientOriginalName());
$bovino->imagen = $file->getClientOriginalName();
}
//This is how im trying to update a parent
$id_padre = $bovino->padres()->pluck('id_padre')->toArray();
$bovino->padres()->updateExistingPivot($id_padre, ['id_padre' => $request->get('padre')]);
$bovino->update();
return redirect('/ganado/engorde');
}
And finally, I want to update both parents (father and mother), and I have 2 "select" where I show all of them.
Code to update one by one comparing them with the gender:
// $padres is a collection containing both parents
$padres = $bovino->padres()->get();
// foreach loop to differentiate the mother and father
foreach($padres as $padre){
if($padre->genero === GENERO_MUJER){
//mother
} else {
//father
}
}
I want to update multiple Departments against one unit. I tried this method, but it's not correct.
How can I update multiple departments ids?
Form:
Request:
Controller Function:
$pre_data = UnitDepartment::where('unit_id', $request->id)->get();
if ($pre_data) {
foreach ($pre_data as $value) {
$value->delete();
}
$department = $request->department_id;
foreach ($department as $value) {
$unitDepart = new UnitDepartment();
$unitDepart->unit_id = $request->id;
$unitDepart->department_id = $value;
$unitDepart->save();
}
}
table:
I found that is the table related to departments and units.
So you can build the relationship many-to-many between them,
Create the relationship in your models,
In Unit model:
public function departments()
{
return $this->belongsToMany('App\Unit','unit_department','unit_id','department_id');
}
In Department Model:
public function units()
{
return $this->belongsToMany('App\Department','unit_department','department_id','unit_id');
}
Attach the new relationship, simply use:
Unit::find($request->unit_id)->departments()
->sync($request->department_id);
Unfortunately, you cannot use softDelete on sync().
And I don't think you need to soft delete with unit_departments. As a pivot then it should be irrelevant if it is deleted or not.
And if user update the relationship on the frequent, this table will grow fast.
If you really need to soft-delete, you can write it like this:
$department_ids = $request->department_id;
$unit_id = $request->unit_id
// soft delete the unit_departments not in request:
UnitDepartment::where('unit_id', $unit_id)->whereNotIn('department_id', $department_ids)->delete();
// insert the new department_id+unit_id relationship
$exist_department_ids = UnitDepartment::where('unit_id', $unit_id)->whereIn('department_id', $department_ids)->pluck('department_ids')->all();
$dept_ids = array_diff($exist_department_ids, $department_ids);
$depts = collect($dept_ids)->map(function($dept_id) use ($unit_id) {
return ['department_id' => $dept_id, 'unit_id' => $unit_id];
});
UnitDepartment::insert($depts);
the problem is you're sending unit_id in the request, however using $request->id in the query which is wrong.
Change every occurance of $request->id with $request->unit_id in the controller.
to select pre data correctly
use
$pre_data = UnitDepartment::where('unit_id', $request->id)->first();
i tried this
$unit = UnitDepartment::where('unit_id', $request->unit_id)->get();
foreach ($unit as $item) {
$existDepartment[] = $item->department_id;
}
$newDepartment = $request->department_id;
$result = array_diff($newDepartment, $existDepartment);
if ($result) {
foreach ($result as $item) {
$data = new UnitDepartment();
$data->unit_id = $request->unit_id;
$data->department_id = $item;
$data->save();
}
}
Given a model and an id. Get all $fillable data for that row. This is including data from relationships between models, so if there's a relationship with another model. It needs to fetch all fillable data from the related model too. And if that related model has relationships, we need to follow those as well.
I've tried lots of stuff so far but it's all following the same general thought process. What I have so far on my most recent attempt is below. Further information: Each model has protected $fillable and an array called $getPossibleRelations which has a list of relationship names used by that model. There are __get functions for fillable, and possible relations on the models.
$item = $model->where('id',$id);
function deepProcess($item) {
$fillable = $item->find(1)->getFillable();
$item = call_user_func_array(array($item, 'select'), $fillable);//select only fillable fields
$possibleRelations = $item->find(1)->getPossibleRelations();
foreach ($possibleRelations as $rel) {//eager load any possible relations
$item = $item->with([
$rel => function($query) {//reaches here ok, below recursion fails
$query = deepProcess($query);
}
]);
}
return $item;
}
$item = deepProcess($item)->get()->toArray();
dd($item);
Within the eager load the query needs to somehow loop back through the same function. And it needs to make sure it doesn't go back through a relation it has already been through (I used get_class() to check for that in a previous attempt).
I'm a bit lost as to how I should do this
Here's another attempt I made which is also flawed in many obvious ways.
$item = $model->where('id',$id);
$checkedModels = [];
$result = [];
function deepFetch($item,&$result,&$checkedModels,$className) {
if (in_array($className,$checkedModels)) {
return; //we've already added bits from this model
}
array_push($checkedModels,$className);
if($className == 'Illuminate\Database\Eloquent\Collection') {
dd($item);
}
var_dump('loop count');
$fillable = $item->get()[0]->getFillable();
$possibleRelations = $item->get()[0]->getPossibleRelations();
foreach($item->select($fillable)->get() as $row) {
array_push($result,$row);
}
foreach ($possibleRelations as $rel) {
dd($item->get());
$newItem = $item->get()->$rel;
deepFetch($newItem,$result[$rel],$checkedModels,get_class($newItem));
}
}
deepFetch($item,$result,$checkedModels,get_class($model));
$item = $model->where('id',$id);
function deepProcess($item,$depth,$currentRel,$currentRelClasses) {
foreach ($depth->first()->getPossibleRelations() as $rel) {//eager load any possible relations
$newRel = $rel;
if ($currentRel !== '') {
$newRel = $currentRel.'.'.$rel;// $newRel example, dog.owner.addresses
}
$futureItemCollection = $depth->first()->$rel->first();
if (!$futureItemCollection) {
continue; // no relationship found from $depth through $rel
}
array_push($currentRelClasses, get_class($futureItemCollection));//we need to check for the future relationship before the recursive call
if (max(array_count_values($currentRelClasses)) > 1) {//if we've hit the same relation more than once then skip current iteration
continue;
}
// $fillable = $futureItemCollection->getFillable();
// $item = $item->with([$newRel => function($query) use($fillable) {
// call_user_func_array([$query, 'select'], $fillable);//select only fillable fields
// }]);
$item = $item->with($newRel);//selecting only fillable fields wasn't working, likely due to unexpected fields being required
$item = deepProcess($item, $depth->first()->$rel,$newRel,$currentRelClasses);
}
return $item;
}
$item = deepProcess($item,$item,'',[get_class($item->first())])->get()->toArray();
dd($item);
Mostly works, it's rough, and I couldn't get the select statement to work (which is why it's commented out and a plain with(relation) is used instead. It works for my purposes though
I do not understand how to do the following:
Lets say I have a product table, and a photo table. 1 Product has many photos. So in the product model I do:
var $has_many = array("category", "photo");
Now I want to get all products and relate each of their photos to them. How can I do this? Currently, in my controller I am going through each of the products and querying photos and passing a separate array that way. This CANNOT be ideal. I should be able to tie each photo to the specific product directly no?
Logically, this would work (but it doesnt?)
$product = new Product;
$products = $product->get_by_related_category('name', $where);
$photos = $product->photo->get();
See what I'm getting at? I would love to just pass that $products variable to my view, be able to foreach through it, and have an array of photos tied to each product object.
How can I accomplish this? Or is there a better way to do this?
Thanks!
With a "has many" relation you basically have two way to fetch the related information with SQL:
You can join the other table in like select products.*, photos.* from products left outer join photos on products.id = photos.product_id. This way you will have "duplicate" products data so you need to handle the results accordingly. Unfortunately include_related() doesn't support this directly, it would create the duplicated products with each of them have one related photo in your case.
You can run two queries, first fetching the products (select * from products where ...) and then fetching the photos with the id's of the selected products (select * from photos where product_id in (...)) and sort out what photos row should go what product. There's no built-in functionality for this in DMZ, but here's what I've coded up for a model base class (that extends the DataMapper class) that can be used like this:
$products = new Product;
$products = $products
->get_by_related_category('name', $where) // first get the parent objects
->load_related('photo'); // then load in the related ones inside them
foreach ($products as $product) {
// unique product instances as before
foreach ($product->photo as $photo) {
// and every product has a list of related photos
// for each product individualy
}
}
The method below will gather the id's of the parent objects, run one SQL query with the ids in a where_in() and sort the results out for the parent object's related field object (unfortunately its a little long and doesn't support many-to-many relations).
/**
* load_related
*
* Loads related has_many objects efficiently
*
* #param string $related_field_name the name of the relation
* #param callable $filter_function callback to place extra conditions on the related model query
*/
public function load_related($related_field_name, $filter_function = null) {
$related_properties = $this->_get_related_properties($related_field_name);
$related_models = new $related_properties['class'];
$join_field = $related_properties['join_self_as'].'_id';
$ids = array_unique(array_filter(array_merge(array('id' => $this->id), array_column($this->all, 'id')), 'intval'));
if (empty($ids)) {
return $this;
}
$related_models->where_in($join_field, $ids);
if (is_callable($filter_function)) {
call_user_func($filter_function, $related_models);
}
$related_models = $related_models->get();
$related_models = array_group_by($related_models, $join_field);
foreach ($this->all as $i => $row) {
$related_models_to_row = isset($related_models[$row->id]) ? $related_models[$row->id] : null;
if ($related_models_to_row) {
$this->all[$i]->{$related_field_name} = reset($related_models_to_row);
$this->all[$i]->{$related_field_name}->all = $related_models_to_row;
}
}
if (isset($related_models[$this->id])) {
$this->{$related_field_name} = $related_models[$this->id];
}
return $this;
}
// The two array helper functions used above from my_array_helper.php
function array_group_by($arr, $key, $index_by_col = false) {
$re = array();
foreach ($arr as $v) {
if (!isset($re[$v[$key]])) {
$re[$v[$key]] = array();
}
if ($index_by_col === false) {
$re[$v[$key]][] = $v;
} else {
$re[$v[$key]][$v[$index_by_col]] = $v;
}
}
return $re;
}
function array_column($arr, $key, $assoc = false) {
if (empty($arr)) {
return array();
}
$tmp = array();
foreach ($arr as $k => $v) {
if ($assoc === true) {
$tmp[$k] = $v[$key];
} elseif (is_string($assoc)) {
$tmp[$v[$assoc]] = $v[$key];
} else {
$tmp[] = $v[$key];
}
}
return $tmp;
}
I'm kinda exploring DM for a while now and I needed the same functionality. At first the load_related function from the other answer seemed to be the solution for this.
I did some further research though. I found this answer to another question and it made me thinking if there wasn't a way to autopopulate only some of the relations.
Well, there is !!
You can set this 'option' if you make a relation in a model.
//Instead of doing this:
var $has_many = array('user_picture');
//Do this
var $has_many = array(
'user_picture' => array(
'auto_populate' => TRUE,
),
);
Now the pictures will be available in a user object.
foreach ($u as $user) {
foreach ($user->user_picture as $picture) {
// Do your thing with the pictures
}
}
I found it on this page from the docs.
Enjoy!
i started using cakephp, but now i encountered a problem which i am not able to solve.
I have got the following model relations which are relevant:
Exercise hasMany Points
Student hasMany Points,
now i want to check in the studentsController if for every exercise there is a Point data set, and iff not insert a new one.
When starting with no Point datasets, the function adds a new Point dataset for the first exercise correct, but after this it only updates the erxercise_id of this dataset, instead of creating new ones.
The controller function looks like this:
public function correct($id = null) {
$this->Student->id = $id;
$this->Student->recursive = 2;
if ($this->request->is('get')) {
$data = $this->Student->Exam->Exercise->find('all');
foreach($data as $exercise)
{
$exerciseID = $exercise['Exercise']['id'];
$this->Student->Point->recursive = 0;
$foundPoints = $this->Student->Point->find('all', array('conditions' => array('exercise_id' => $exerciseID)));
if($foundPoints == null)
{
$emptyPoints = array ('Point' => array(
'exercise_id' => $exerciseID,
'student_id' => $id
)
);
$this->Student->Point->save($emptyPoints);
}
else{
}
}
}
else //POST
{
}
}
if you have to insert a data you to use create() method like this:
This is only an example, with this line you create every time a new record into your database
and save data
$this->Student->Point->create();
$this->Student->Point->save($emptyPoints);
$this->Student->Point->id = '';
$this->Student->Point->save($emptyPoints);
or
$this->Student->Point->saveAll($emptyPoints);