Laravel - Get variable from a DB Transaction Closure - php

I am working with a Laravel 5 LAMP stack, and I am trying to process a CSV import with a database transaction. Code looks like this:
// Should contain any messages to pass back to the user
$results = [];
// Contains the total records inserted
$total = 0;
DB::transaction(function() use($csv_file, $results, $total) {
// Some Code ...
$total++;
$results[] = 'Row 10 has some weird data...';
});
return view('plan.import')
->with('results', $results)
->with('total', $total);
At the end of that, my records are imported, but my $total and $results are still empty, since they are outside the scope of the closure. I know they are being altered inside the function, because I've stepped through it, and seen them change. I just can't figure how to get them out of that transaction and return them to the user. Can anyone please help with this?

You may replace the following line:
DB::transaction(function() use($csv_file, $results, $total)
with this:
DB::transaction(function() use($csv_file, &$results, &$total)
So the changes made inside the function will reflect in the variables because & creates a reference of the variable (Passes the variable reference) instead of passing them by value. Check Passing by Reference Manual.
Alternatively, you can return the variables from inside the closure like:
$array = DB::transaction(function() use($csv_file, $results, $total) {
// Some Code ...
$total++;
$results[] = 'Row 10 has some weird data...';
return compact('total', 'results');
});
Then use it like:
return view('plan.import')
->with('results', $array['results'])
->with('total', $array['total']);

Related

Laravel: Object of class Illuminate\Database\Eloquent\Builder could not be converted to string

I am trying to loop through an array of ids to get data from another table, I mean I want to get latest queues of every schedule id we are looping in it.
So first i have $schedules:
$schedules = [{"id":19},{"id":18},{"id":15}]
And the foreach loop is like this:
$queues= [];
foreach ($schedules as $schedule) {
$queues[] = Queue::withTrashed()
->latest()
->where('schedule_id', $schedule);
}
return $queues;
when i return queues it's showing :
Object of class Illuminate\Database\Eloquent\Builder could not be converted to string
The error that shows is related to you are not running the query, you need a ->get() at the end of Queue::...->where(...)->get() to do it.
if you dont do it, as Dhananjay Kyada say in his answer:
it will try to return a query object
But to return a response:
The Response content must be a string or object implementing __toString()
Next, we need to tackle one more thing.
If you are defining the variable $schedules and assigning it the value as it is shown in the question:
$schedules = [{"id":19},{"id":18},{"id":15}]
try to do it taking the JSON as string and converting it into a PHP variable with json_decode:
$schedules = json_decode('[{"id":19},{"id":18},{"id":15}]');
Then you can use the id values in you where clause:
->where('schedule_id', $schedule->id)->get()
Maybe because you are not getting the result from your query. Because it is just a query it will return a query object. You need to add ->get() in order to get the result. You can try the following code:
$queues = [];
foreach ($schedules as $schedule) {
$queues[] = Queue::withTrashed()
->latest()
->where('schedule_id', $schedule)->get()->toArray();
}
return $queues;

Laravel: Where consult in a foreach loop not working

I dont know what am I doing wrong, I have a foreach loop and a where consult inside it and when I try to get the data through that object, it says that it's and non-object and I cant get the data.
public function actualizar_costo_promedio()
{
$empresas = Empresa::all();
$anteriores_exist = 0;
foreach ($empresas as $empresa) {
$producto = $empresa->productos()->where('producto_nombre_id', 1)->first();
$anteriores_exist += $producto->existencias;
}
}
If i do this, it gives me that error but if I change the where for only $producto = $empresa->productos()->first(); it works
I've made other tests with $empresa = Empresa::find(1); and after do the consult $producto = $empresa->productos()->where('producto_nombre_id', 1)->first(); and also it works, so I don't understand what it's wrong
So let me tell you what is wrong, this piece of code:
$empresas = Empresa::all();
returns a collection or an array of arrays.
Now when you are foreaching you are iterating over the Empresa, but then you are accessing a relationship
$empresa->productos()
which from the error I can tell that it's a one-to-many relationship since the error is happening because the relationship is returning you another collection and you are trying to access a property which throws the error.
But when you pick up the first item in collection you actually access the first array in that array containing all the arrays! Therefore this is working:
$producto = $empresa->productos()->where('producto_nombre_id', 1)->first();
So if you want to make it work you need another iteration inside the foreach, or as I can understand correctly you want to get the existing products foreach Empresa you could do:
$anteriores_exist = $empresas->map(function($row) {
$products = $row->productos()->where('id', 1)->get();
return $products->filter(function($row) {
return $row->existencias;
});
});

Laravel - efficient way to check database parameters before insert

I have populate a form of which every text field generated is based on the database result. I simply name every text field using the id. Now when the form is filled, I use controller to save it. But prior to insert the database, I loop the Request::input() to check every item whether such entry is exist or not. I just wonder if there is efficient way to check every item in the loop to insert it into db. Here is my code
public function store(Request $request, $id, $inid)
{
$startOfDay = Carbon::now()->startOfDay();
$endOfDay = Carbon::now()->endOfDay();
$instruments = InstrumentReading::whereBetween('created_at', [$startOfDay, $endOfDay])
->where('iv_inid', '=', $inid)
->get();
foreach ($request->input() as $k => $v) {
$read = new InstrumentReading;
$read->iv_inid = $inid;
$read->iv_ipid = $k;
$read->iv_usid = Auth::user()->id;
$read->iv_reading = $v;
$read->save();
}
if ($instruments->count() > 0) {
//to filter the iv_ipid...
foreach($instruments as $instrument)
{
$instrument->iv_status = "VOID";
$instrument->save();
}
}
}
In words of efficent approach what you can do is to simple check / fetch ONLY all posible rows from the database, and the check in the loop if the row was already inserted. Also fetch only iv_ipid column, as we do not need all columns from the table to do our check. It will be faster to select only the column we need. You can use directly Fluent (Query Builder) over Eloquent to pull the data from database as it greatly increase the performance for a simple query like this.
public function store(Request $request, $id, $inid)
{
// Search only records with submitted iv_ipid, iv_inid and created today
$alreadyInserted = DB::table('instrument_readings')
->whereBetween('created_at', [
Carbon::now()->startOfDay(),
Carbon::now()->endOfDay()
])
// Get only records with submitted iv_ipid
->whereIn('iv_ipid', array_keys($request->input()))
// Get records with given iv_inid only
->where('iv_inid', $inid)
// For our check we need only one column,
// no need to select all of them, it will be fast
->select('iv_ipid')
// Get the records from DB
->lists('iv_ipid');
foreach ($request->input() as $k => $v) {
// Very simple check if iv_ipid is not in the array
// it does not exists in the database
if (!in_array($k, $alreadyInserted)) {
$read = new InstrumentReading;
$read->iv_inid = $inid;
$read->iv_ipid = $k;
$read->iv_usid = Auth::user()->id;
$read->iv_reading = $v;
$read->save();
} else {
//todo
}
}
This is the most efficent way suggested until now, because you fetch at once only the records you are interested in, not all records from today. Also you fetch only one column, the one that we need for out check. Eloquent ususlally give a lot of overheat on the perfomance, so in the suggested code I use directly Fluent, which will boost the speed this part of code is executed by ~ 20%.
Your mistake in the original code is that you are doing database call each time in a loop. When you need such a simple task as a check, never put database calls, queries etc. in a loop. It is an overkill. Instead select all needed data before the loop and then do your checks.
Now this is in case you only need to save new records to database. In case you want to manipulate each record in the loop, let's say you need to loop through each submited entry, get get the model or create it if it does not exists and then do something else with this model, the most efficent way then will be this one:
public function store(Request $request, $id, $inid)
{
foreach ($request->input() as $k => $v) {
// Here you search for match with given attributes
// If object in DB with this attributes exists
// It will be returned, otherwise new one will be constructed
// But yet not saved in DB
$model = InstrumentReading::firstOrNew([
'iv_inid' => $inid,
'iv_ipid' => $k,
'iv_usid' => Auth::user()->id
]);
// Check if it is existing DB row or a new instance
if (!$model->exists()) {
// If it is a new one set $v and save
$model->iv_reading = $v;
$model->save();
}
// Do something with the model here
.....
}
This way Laravel will check if model with the passed parameters already exist in database, and if so it will return it for you. If it does not exist, it will create new instance of it, so you can then set the $v and save to db. So you are good to go to do anything else with this model and you can be sure it exists in database after this point.
First approach (efficiency first)
Consider using a simple SQL INSERT IGNORE Query and use Fluent, i.e.:
Make a composite unique key containing:
iv_inid
iv_ipid
created_time, up to an hour granularity, this is important, because created_at might have a far greater granularity than your intended purpose, and might slow things down a bit.
Use DB, i.e.:
DB::query(
"INSERT IGNORE INTO $yourTable VALUES ( ... )"
);
Pros:
- Extremely fast, all the necessary checking is done on the DB Server
Cons:
- You cannot know which values triggered a duplicate value / unique key violation, as related errors are treated as warnings.
Second approach (convenience first)
Use firstOrFail, i.e.:
$startOfDay = Carbon::now()->startOfDay();
$endOfDay = Carbon::now()->endOfDay();
// ... for
try {
InstrumentReading::where('iv_inid', $inid)
->where('iv_ipid', $k)
->whereBetween('created_at', [$startOfDay, $endOfDay])
->firstOrFail();
continue;
} catch (ModelNotFoundException $e) {
$instrumentReading = InstrumentReading::create([
// your values
]);
}
// ... endfor
Pros:
- Easy to implement
Cons:
- Somewhat slower than simple queries
Your code will send request to database every time you need to check the value. Instead, search all value of this day then check the value. This approach will send request to database only one time.
$startOfDay = Carbon::now()->startOfDay();
$endOfDay = Carbon::now()->endOfDay();
// Search only this day
$instruments = InstrumentReading::whereBetween('created_at', [$startOfDay, $endOfDay])->get();
foreach($instruments as $instrument)
{
// Check the value
}

add data on a array query result

I have the function below in my model which return an array of data. I want to make another operation on each row and add the result to array of data before to return it.
function get_all_annonce()
{
$this->db->select(" * from annonce");
$this->db->order_by("annonce.DATEDEBUTANNONCE","asc");
$q=$this->db->get();
if($q->num_rows()>0)
{
foreach($q->result() as $row)
{
$data[]=$row;
//I try to add another result
$experience=$this->get_experience($row->NUMUSER);
$data['experience']=$experience;
}
return $data;
}
}
But I have an error when I try to access to $experience or $row->'experience' in my view. How can I fix it ?
The $data variable is defined in the wrong scope. You defined it inside the foreach loop but you try to return it after. Try adding $data = Array(); above the foreach.
In addition to the answer above.
First you have unnecessary assignments, you can do it in one line.
2nd - when you use [] - it will create index automatically and the row will be added as an array to that index. You get a multidimensional array( 0 => result 1, 1 => result 2 etc).
If you want to add the 'experience' key to the result, you cannot add it directly to data.
You get an array that will have keys 0,1,2,3,4 ... 'experience' as last key - each time it is overwritten.
One way would be to use a variable for key (or use for loop instead):
$i = 0;
foreach($q->result() as $row)
{
$data[$i]=$row;
$data[$i]['experience'] = $this->get_experience($row->NUMUSER);
}
If you used only [] for both, it would assign different key for each one every iteration.

How to get the Last data from Database using Laravel

I am trying to get a column of the last data in the database but so far, I am getting all the data in the database in an array.
This is my code;
public function NewID(){
$adminid=Auth::user()->admin_id;//cooperative ID
$newid = Member::select('member_id')->where('admin_id', '=',$adminid)->get();
return View::make('admin.member.addmember')
->with('NewID', $newid);
}
I have updated the code to be this base on suggestions;
public function NewID(){
$adminid=Auth::user()->admin_id;//cooperative ID
$newid = Member::select('member_id')->where('admin_id', '=',$adminid)->orderBy('id', 'desc')->first();
return View::make('admin.member.addmember')
->with('NewID', $newid);
}
and I am using a For Loop to display data on the view
#foreach ($NewID as $NewIDs)
{{$NewIDs->member_id}}
#endforeach
My error is now ErrorException:Trying to get property of non-object
Answer
I finally got it to work
I used this instead
public function NewID(){
$adminid=Auth::user()->admin_id;//cooperative ID
$newid = Member::select('member_id')->where('admin_id', '=',$adminid)->orderBy('id', 'desc')->take(1)->get();
return View::make('admin.member.addmember')
->with('NewID', $newid);
}
I finally got it to work
I used this instead
public function NewID(){
$adminid=Auth::user()->admin_id;// ID
$newid = Member::select('member_id')->where('admin_id', '=',$adminid)->orderBy('id', 'desc')->take(1)->get();
return View::make('admin.member.addmember')
->with('NewID', $newid);
}
Well, a lot of things could be wrong, so what you should do is find the cause. In you view file, temporarily remove the foreach loop and replace it with {{ dd($NewID) }}. This will 'dump and die' the value of $NewID.
Additionally, I suggest you stick to variable naming conventions. Variables should start lowercase. Also it is confusing to call a collection of Members NewID and a single instance of a member NewIDs. Sticking to the convention helps you and others to read and debug your code.

Categories