Laravel/LaraCSV Managing Complex Relationships - php

I'm running into some issues with my Collection/Model relationships with regards to LaraCSV. Here is its documentation: https://github.com/usmanhalalit/laracsv#full-documentation. I have 3 models that interact right now: Doctor, Patient, Script
Doctor belongsToMany Patient
Patient belongsToMany Doctor
Patient hasMany Script
Script belongsTo Patient
I also created a relationship link inside of my Doctor model that can be used to tie Doctor to Script, but does not appear to work in this instance:
public function scripts() {
$this->load(['patients.scripts' => function($query) use (&$relation) {
$relation = $query;
}]);
return $relation;
}
What I am attempting to do is allow admin staff and our users to download CSV files that contain all of their scripts. While this works fine for admin staff as I can reference the models directly, I am not able to make it work for users because they are tied to the doctors, and I cannot seem to tie this into scripts as normal. Here is a perfectly working version for admin staff:
$doctors = Doctor::orderBy('last_name', 'asc')->get();
$patients = Patient::orderBy('last_name', 'asc')->get();
$scripts = Script::orderBy('prescribe_date', 'desc')->get();
$csvExporter->beforeEach(function ($script) use ($doctors, $patients) {
$patient = $patients->where('id', $script->patient_id)->first();
$doctor = $patient->doctors->first();
$script->patient = $patient->full_name;
$script->doctor = $doctor->full_name;
});
Here is how the user-specific version appears:
$doctors = User::
find(Auth::user()->id)
->doctors()
->orderBy('last_name', 'asc')
->get();
$patients = Patient::orderBy('last_name', 'asc')->get();
$scripts = $doctors->scripts()->get();
Trying to chain in my Doctor model scripts() function results in an error: Method Illuminate\Database\Eloquent\Collection::scripts does not exist.
$doctors = User::
find(Auth::user()->id)
->doctors()
->orderBy('last_name', 'asc')
->get();
$patients = array();
$scripts = array();
foreach ($doctors as $doctor_fe) {
foreach ($doctor_fe->patients as $patient_fe) {
$patients[] = $patient_fe;
foreach ($patient_fe->scripts as $script_fe) {
$scripts[] = $script_fe;
}
}
}
I also tried to pull the information using arrays, but unfortunately, it must be a Collection passed in via this error: Argument 1 passed to Laracsv\Export::addCsvRows() must be an instance of Illuminate\Database\Eloquent\Collection, array given

I settled by placing all of the patients belonging to the user's doctors through a foreach loop, then using another one to grab the patient's id. I then took the patient's id array and used the whereIn function to compare the Script's patient_id field to get the correct strips.
$doctors = User::
find(Auth::user()->id)
->doctors()
->orderBy('last_name', 'asc')
->get();
$patients_array = array();
foreach ($doctors as $doctor_fe) {
$patients_fe = $doctor_fe->patients;
foreach ($patients_fe as $patient_fe) {
$patients_array[] = $patient_fe->id;
}
}
$patients = Patient::orderBy('last_name', 'asc')->get();
$scripts = Script::whereIn('patient_id', $patients_array)->get();
$csvExporter->beforeEach(function ($script) use ($doctors, $patients) {
$patient = $patients->where('id', $script->patient_id)->first();
$patient_initials = substr($patient->first_name, 0, 1) . substr($patient->last_name, 0, 1);
$doctor = $patient->doctors->first();
$script->patient = $patient_initials;
$script->doctor = $doctor->full_name;
});

If I interpret you question correctly, you want to get all scripts of all patients of a doctor. Laravel provides the hasManyThrough() Method for this:
class Doctor extends Model
{
/**
* Get all of the scripts for the patient.
*/
public function scripts()
{
return $this->hasManyThrough(App\Script::class, App\Patient::class);
}
}
The first param is the model you want to get (the scripts); the 2nd param is the intermediate model (the patient).
To use it:
$doctor = Doctor::first();
$scripts = $doctor->scripts;

Related

Update multiple ids against single id (Laravel)

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

Passing a variable to a query creates the error "Trying to get property 'name' of non-object" with Laravel

I am using Laravel 6. I am trying to create a validation system with a form to create a meeting.
When a user creates a meeting with participants that are already occupied in another meeting, a message should appear in the view with the name of the participants already occupied.
For some reason the function that should find the name of the participants doesn't work. I pass an id during a foreach loop but when I run the form appears the following message: "Trying to get property 'name' of non-object".
The strange thing is that the id passed to the function are OK, but if I write a number (for example "8") in place of $id in the query appears correctly the name "Chris" in the view.
The format of the column "id_participants" in the table meetings is the following "23;7;6".
<?php
namespace App\Rules;
use Illuminate\Contracts\Validation\Rule;
use DB;
use App\User;
class CheckParticipant implements Rule
{
protected $participants_occupied = array();
/**
* Create a new rule instance.
*
* #return void
*/
public function __construct()
{
//
}
/**
* Determine if the validation rule passes.
*
* #param string $attribute
* #param mixed $value
* #return bool
*/
public function passes($attribute, $value)
{
$participants = request('participants');
foreach($participants as $participant) {
$meetings = DB::table('meetings')
->where('is_active', '1')
->where('date', request('date_meeting'))
->where(function ($query) {
$query->where(function($sub_q) {
$sub_q->where('start_hour', '>=', request('start'))
->where('start_hour', '<', request('end'));
})
->orWhere(function($sub_q) {
$sub_q->where('start_hour', '<', request('start'))
->where('end_hour', '>=', request('end'));
})
->orWhere(function($sub_q) {
$sub_q->where('end_hour', '>', request('start'))
->where('end_hour', '<=', request('end'));
});
})
->where(function ($query) use($participant) {
$query->where('id_participants', $participant)
->orWhere('id_participants', 'like', '%;'.$participant)
->orWhere('id_participants', 'like', $participant.';%')
->orWhere('id_participants', 'like', '%;'.$participant.';%');
})
->get();
if(count($meetings) > 0) {
array_push($this->participants_occupied, $participant);
}
}
if(count($this->participants_occupied) > 0) {
return false;
} else {
return true;
}
}
/**
* Get the validation error message.
*
* #return string
*/
public function message()
{
for($i = 0; $i < count($this->participants_occupied); $i++) {
$this->participants_occupied[$i] = $this->getNameSurnameById($this->participants_occupied[$i]);
}
return 'The participants are already occupied at that time: ' . implode(',', $this->participants_occupied);
}
public function getNameSurnameById($id)
{
$users = User::all()->where('id', 18)->first(); //if I write a number in place of $id everything works
return $users->name;
}
}
I would like that this program works dynamically. I suppose there is something wrong in the query with the variable $id. Is someone able to help me?
UPDATE:
I solved the problem modifying the message function as follows:
public function message()
{
$arr_names = array(); //I created this array
for($i = 0; $i < count($this->participants_occupied); $i++) {
array_push($arr_names, $this->getNameSurnameById($this->participants_occupied[$i]));
}
return 'The following participants are already occupied at that time: ' . implode(', ', $arr_names);
}
I suppose that the problem consisted that I gave a string value (the name of the participant) to an array that had integers values (The id of the participant). I solved creating a new empty array and I pushed the names to the new array.
You may find it much easier to grab your ids based on some type of Laravel object, rather than an array. I suspect that the array has an incorrect value (not an id) at the index of $i during the loop at some point. And, as pointed out in the comments by #Cristóbal Ramos Merino, you are setting the variable to a potential string (the user name) at the same time as you are trying to pass the possible id through to the getNameSurnameById() method.
I would grab all of the ids passed from the form, do a DB query on User to see who is already occupied, and then just pull the name from the resulting collection.
Something like:
$allFormUsers = User::whereIn('id', $formIds)->get();
Then loop on this to get the names of those occupied:
$occupiedNames = [];
foreach($AllFormUsers->where('occupied', 1) as $u){
$occupiedNames[] = $u->name;
}
I have no idea how you are tracking the occupied - and so the above code is little more than pseudo code, but hopefully will give you an idea of how to do this without the array / concurrency. This also is a little less work on the Database, since you have one query, instead of looping on individual queries each time. You can even pull all users first so you have them stored, and then do a where('occupied', 1) against the collection if you like, as in the above loop. (Assuming that's how you track occupied)

How to merge multiple Eloquent collections and sort them by timestamp?

So I have two collections one has posts made by the user that's logged in the other is posts made by users that he is following. I want to merge these two collections together and then sort them as one collection.
this is what I tried:
$feed = $friendPosts->merge($posts);
$feed->sortByDesc('created_at');
The problem is that they do get merged together, but the sort function seems to not work. instead of them being mixed together they come in parts. so all the $friendPosts posts come and the other posts come after wards
I'm using slim framework along with twig and eloquent. here's the entire controller that this is being used in, just for some context:
public function getSessionProfile($request, $response)
{
//return to sign in if not signed in
if ($_SESSION['user'] == null) {
return $this->view->render($response, 'templates/signin.twig');
}
//grab current user
$user = User::where('id', $_SESSION['user'])->first();
//grab the user's followers
$followers = Follow::where('follower_id', $user->id)->get();
//user posts
$posts = $user->posts;
//get the posts made by friendllowers
foreach ($user->follows as $follow) {
$follow = User::where('id', $follow->follower_id)->first();
//posts by people that user is following
$friendPosts = $follow->posts;
}
//append author to each post made by friendllowers
$friendPosts->map(function ($post) {
$postAuthor = User::where('id', $post->user_id)->first();
$post['authorName'] = $postAuthor->name;
$post['authorPicture'] = $postAuthor->avatar;
return $post;
});
//append an author to each post made by user
$posts->map(function ($post) {
$postAuthor = User::where('id', $post->user_id)->first();
$post['authorName'] = $postAuthor->name;
$post['authorPicture'] = $postAuthor->avatar;
return $post;
});
$feed = $friendPosts->merge($posts);
$feed->sortByDesc('created_at');
$this->container->view->getEnvironment()->addGlobal('User', $user);
//posts by user
$this->container->view->getEnvironment()->addGlobal('Posts',$user->posts);
//feed
$this->container->view->getEnvironment()->addGlobal('Feed',$feed);
// TODO: make two twig templates one for user only one for feed
$this->container->view->getEnvironment()->addGlobal('Followings', $user->follows);
$this->container->view->getEnvironment()->addGlobal('Followers', $followers);
return $this->view->render($response, 'home.twig');
}
I fixed the problem:
needed to change $feed->sortByDesc('created_at'); to
$feed = $feed->sortByDesc(function($post)
{
return $post->created_at;
});

Fill data from Model and function into an Associative array in Laravel 5.2

I'm working with Laravel for the first time. I have a scenario where I have a Products table which contains basic details of a Product (Corrugated Box) like length, breadth, height etc. Some other details of the product is computed using the basic details within a function.
My code in the Controller looks like this:
public function viewProducts() {
/* Fetch basic details */
$prod_specs = DB::table('master_products')
->join('part_types', 'master_products.part_type_id', '=', 'part_types.id')
->join('box_types', 'master_products.box_type_id', '=', 'box_types.id')
->select('master_products.*', 'part_types.part_type', 'box_types.box_type')
->get();
/* Calculate Specs and add them to the array */
$i = 1;
$products = array();
foreach ($prod_specs as $spec) {
$products['product_code'] = $spec->product_code;
$products['part_type_id'] = $spec->part_type_id;
$products['box_type_id'] = $spec->box_type_id;
$products['length'] = $spec->length;
$products['breadth'] = $spec->breadth;
$products['height'] = $spec->height;
$products['ply'] = $spec->ply;
$products['gsm_a_base'] = $spec->gsm_a_base;
$products['gsm_a_flute'] = $spec->gsm_a_flute;
$products['gsm_b_base'] = $spec->gsm_b_base;
$products['gsm_b_flute'] = $spec->gsm_b_flute;
$products['gsm_top'] = $spec->gsm_top;
$products['roll_size'] = $this->calcRollSize($spec->height, $spec->breadth, $spec->ply, $spec->part_type_id, $spec->box_type_id);
}
return view('/layouts/masters/products-master', ['products' => $products]);
}
/* Calculate Roll Size */
private function calcRollSize($height, $breadth, $ply, $partTypeID, $boxTypeID) {
/* Some calculation */
return $rollSize;
}
I want to return $products to my view and be able to access the basic details as well as the calculated details. Please help me achieve this.
UPDATE
I tried:
$products = DB::table('master_products')
->join('part_types', 'master_products.part_type_id', '=', 'part_types.id')
->join('box_types', 'master_products.box_type_id', '=', 'box_types.id')
->select('master_products.*', 'part_types.part_type', 'box_types.box_type')
->get();
/* Calculate Specs and add them to the collection */
foreach ($products as $product) {
$rollSize = $this->calcRollSize($product->height, $product->breadth, $product->ply, $product->part_type_id, $product->box_type_id);
$products->put('roll_size', $rollSize);
}
and got this exception: Call to a member function put() on a non-object
But according to this stackoverflow question's accepted answer it's supposed to work. Please help.
Using return view('/layouts/masters/products-master')->with(compact('products')); you can access the full $products variable you built in the products-master view

DataMapper ORM - How to get all of a model and each instance's related data

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!

Categories