Today I came up with a legacy code. I have a function that sometimes generates very long runtimes and often occurs timeout at our partner. Mainly the function checks for reserved seats at a theatre but instead of get all the data and check for empty seats it runs the query for every single seat. which (i think) generates the huge runtime.
Here is the correspondig code:
public function printTicketChaos(){
$input = Input::all();
if( isset($input['date_id']) && !isset($input['reserve_id']) ){
$program_date = \Model\ProgramDate::find($input['date_id']);
if($input['piece'] > $program_date->available_capacity){
//dd($input['piece'] . ' ??? ' . $program_date->available_capacity);
return true;
}
$reserved_seats = Session::get('reserved_seats');
if($reserved_seats != null && count($reserved_seats) > 0){
foreach ($reserved_seats as $row => $seats) {
foreach ($seats as $key => $seat) {
$reserve = \Model\Reserve::whereForeignDateId($program_date->id)->whereTableName('programs')->whereHas('seats', function($query) use($row, $seat){ $query->whereRow($row)->whereSeat($seat); })->first();
if($reserve != null){
return true;
}
$oi = \Model\OrderItem::whereForeignId($program_date->id)->whereTableName('programs')->whereHas('order', function($query){ $query->whereStorno(0); })->whereHas('seats', function($query) use($row, $seat){ $query->whereRow($row)->whereSeat($seat); })->first();
if($oi != null){
return true;
}
}
}
}
}
return false;
}
I think the solution could be that i query all the seats and then check for the empty ones in the foreach. Is this a good idea or what do you think which is the best way to do it?
The code uses Laravel 4.2
Thank you for your aswers!
Okay, i made changes in the query and the logic aswell but it was still slow as hell.
The system uses a 3rd party PDF generator to create the "ticket" for the costumer and this was the "bad guy".
Related
I'm using Laravel for my website and it is very slow. I have a lot of translations on my website and I discovered through Laravel's debugbar that my site runs a lot of duplicate queries to get its translations which, after some testing, is the biggest speed issue.
To get the correct translation, I run this line in my blade:
{!! Translation::getString('translation_slug') !!}
that calls this function:
//TranslationHelper.php
public static function getString(string $string, array $variables = null) : string
{
if ($variables) {
$variables = array_map('strval', $variables);
$variables = array_flip($variables);
$variables = preg_filter('/^/', ':', $variables);
$variables = array_flip($variables);
}
$translatable = Translatable::where('slug', $string)->first();
if ($translatable) {
$language = Session::get("language");
if ($language) {
if ($variables) {
return strtr($translatable->getTranslation($language), $variables);
} else {
return $translatable->getTranslation($language);
}
} else {
if ($variables) {
return strtr($translatable->getTranslation('en'), $variables);
} else {
return $translatable->getTranslation('en');
}
}
} else {
return $string; // not found
}
}
As you can see it runs the getTranslation function from the Translatable model:
//Translatable.php
public function getTranslation(string $language) {
$lang = Language::where('shortcode', $language)->first();
if ($lang) $translation = Translation::where('language_id', $lang->id)->where('translatable_id', $this->id)->first();
else $translation = Translation::where('language_id', 2)->where('translatable_id', $this->id)->first();
if ($translation == null || !$translation || !$lang) {
$translation = Translation::where('language_id', 2)->where('translatable_id', $this->id)->first();
if ($translation) return strip_tags($translation->value, '<br><a><b><i><p><ul><li><strong><del><em><u>');
else return $this->slug;
}
else return strip_tags(Translation::where('language_id', $lang->id)->where('translatable_id', $this->id)->first()->value, '<br><a><b><i><p><ul><li><strong><del><em><u>');
}
I've looked into Laravel Cache and managed to cache the Language models like this:
$languages = Cache::rememberForever('languages', function() {
return Language::all();
});
$lang = $languages->where('shortcode', $language)->first();
This significantly lowered the duplicate queries it made but when I tried the same for the translation models like this:
$translations = Cache::rememberForever('translations', function() {
return Translation::all();
});
if ($lang) $translation = $translations->where('language_id', $lang->id)->where('translatable_id', $this->id)->first();
Laravel gives a fatal error:
Maximum execution time of 30 seconds exceeded
The lines where I run Translation::where gives hundreds of duplicate queries. I have a total of 6246 different translation entries so i'm guessing it has something to do with that. Did i make a mistake or is there maybe a better way to refactor this code and to get rid of the duplicate queries and improve my website's speed?
How do I multiple records in the bills_infos table?
It takes the data and stores in the array variable and even I can access it with the compact function. I have marked from where the problem starts.
public function store(Request $request){
$request->validate([
'from'=>'required',
'to'=>'required',
'textbox1'=>'required',
'rate1'=>'required',
'qty1'=>'required',
]);
$store_bill=new Bills();
$store_bill->from=request('from');
$store_bill->name=request('to');
$store_bill->discount=request('discount');
if(is_null($store_bill->discount))
{
$store_bill->discount=0;
}
$store_bill->save();
$invoice_no=Bills::orderBy('created_at','desc')->pluck('id')->first();
for($i=1;$i<=10;$i++)
{
$item_name[$i]=request('textbox'.$i);
$amount[$i]=request('rate'.$i);
$quantity[$i]=request('qty'.$i);
}
//return compact('item_name','amount','quantity');
//Works fine till here.
/* Not Working From Here. */
for($i=1;$i<=10;$i++)
{
if(!(is_null($item_name[$i]) || is_null($amount[$i]) || is_null($quantity[$i])))
{
$store_bills_info=new Bills_Infos();
$store_bills_info->bills_id=$invoice_no;
$store_bills_info->item_name=$item_name[$i];
$store_bills_info->amount=$amount[$i];
$store_bills_info->quantity=$quantity[$i];
$store_bills_info->total=$amount[$i]*$quantity[$i];
$store_bills_info->save();
//return compact('store_bills_info');
return view('print')->with('invoice_no',$invoice_no);
}
}
return "Invoice can't be created due to some error";
}
You can use laravel's 'insert' function instead.
Eg.
for($i=1;$i<=10;$i++)
{
if(!(is_null($i`enter code here`tem_name[$i]) || is_null($amount[$i]) || is_null($quantity[$i])))
{
$store_bills_info[$i]['bills_id'] =$invoice_no;
$store_bills_info[$i]['item_name'] =$item_name[$i];
$store_bills_info[$i]['amount'] =$amount[$i];
$store_bills_info[$i]['quantity'] =$quantity[$i];
$store_bills_info[$i]['total'] =$amount[$i]*$quantity[$i];
}
}
Bills_Infos::insert($store_bills_info);
As a query in a loop in not a good practice, we should avoid doing so.
It not work because when your loop is in 0 iteration you used return !
And your loop dosnt go to the next iteration .
When you returning some thing in PHP functions codes after that will not be compiled .
So simply move your return after for .
Just wondering if it is necessary to use else {return false;} in my codeigniter model functions or if () {} is enough and it returns false by default in case of failure?
controller:
if ($this->model_a->did()) {
$data["results"] = $this->model_a->did();
echo json_encode($data);
}
model:
public function did()
{
//some code here
if ($query && $query->num_rows() > 0) {
return $query->result_array();
} else {
return false;
}
}
in your controller -- test the negative condition first - if nothing came back from the method in your model
if ( ! $data["results"] = $this->model_a->did() ) {
$this->showNoResults() ; }
else { echo json_encode($data); }
so thats saying - if nothing came back - then go to the showNoResults() method.
If results did come back then its assigned to $data
However - in this situation in the model i would also put ELSE return false - some people would say its extra code but for me it makes it clearer what is happening. Versus methods that always return some value.
I think this is more of a PHP question than a CodeIgniter question. You could easily test this by calling your model methods and var_dump-ing the result. If you return nothing from a method in PHP, the return value is NULL.
As much i have experience in CI returning false is not a plus point, because if you return false here then you need to have a condition back in controller which is useless you should be doing like this will save you at least some code of lines
if ($query && $query->num_rows() > 0) {
return $query->result_array();
} else {
return array();
}
so returning an array will save you from many other errors, like type error.
Okay, so I used to have this code and it worked fine:
$lastpost = ForumPos::where('user_id', '=', Auth::id())->orderby('created_at', 'desc')->first();
if ($validator->fails())
{
return Redirect::to('/forum/topic/'.$id.'/new')
->withErrors($validator->messages());
}
elseif ($lastpost->created_at->diffInSeconds() < 15)
{
return Redirect::to('/forum/topic/'.$id.'/new')
->withErrors('You really need to slow down with your posting ;)');
}
else
{
$new_thread = new ForumThr;
$new_thread->topic = $id;
$new_thread->user_id = Auth::id();
$new_thread->title = Input::get('title');
$new_thread->save();
$new_post = new ForumPos;
$new_post->thread = $new_thread->id;
$new_post->user_id = Auth::id();
$new_post->body = Input::get('body');
$new_post->save();
return Redirect::to('/forum/thread/'.$new_thread->id.'');
}
and this worked fine, until I noticed a little problem so I had to change this a bit to get this:
$hasposted = ForumPos::where('user_id', '=', Auth::id())->count();
if ($validator->fails()){
return Redirect::to('/forum/topic/'.$id.'/new')
->withErrors($validator->messages());
} elseif ($hasposted != 0) {
$last_post = ForumPos::where('user_id', '=', Auth::id())->orderBy('created_at', 'DESC')->first();
if ($last_post->created_at->diffInSeconds() < 15) {
return Redirect::to('/forum/topic/'.$id.'/new')
->withErrors('You really need to slow down with your posting ;)');
}
} else {
$new_thread = new ForumThr;
$new_thread->topic = $id;
$new_thread->user_id = Auth::id();
$new_thread->title = Input::get('title');
$new_thread->save();
$new_post = new ForumPos;
$new_post->thread = $new_thread->id;
$new_post->user_id = Auth::id();
$new_post->body = Input::get('body');
$new_post->save();
return Redirect::to('/forum/thread/'.$new_thread->id.'');
}
Now when I post a thread and get to the if statement inside the elseif statement, I hit a roadblock. I get the following error:
I only get this error when I haven't specified the title variable in the controller so the view gets it, however there shouldn't be a view. Any ideas? :S
Take a look at your elseif block (second condition)
if(...)
{
//first condition
return ...;
}
elseif ($hasposted != 0) {
{
//second condition
$last_post = ForumPos::where('user_id', '=', Auth::id())->orderBy('created_at', 'DESC')->first();
if ($last_post->created_at->diffInSeconds() < 15) {
return Redirect::to('/forum/topic/'.$id.'/new')
->withErrors('You really need to slow down with your posting ;)');
}
}
else
{
//third condition
return ...;
}
When your nested if statement fails
$last_post->created_at->diffInSeconds() < 15
this block finishes, and the rest of the conditional finishes without issuing a Redirect. That is, your nested if statement knows nothing about the third conditional. PHP/Laravel are doing what you told it to -- so tell it to do something else.
This is purely a style suggestion, but I've reached a point where I avoid multiple branch conditionals whenever possible, especially when returning from inside a branch. A style more like
if(...)
{
return Redirect(); //...
}
if(...)
{
return Redirect(); //...
}
if(...)
{
return Redirect(); //...
}
if(...)
{
return Redirect(); //...
}
might look longer on the page, but it's much clearer what's going on.
If this? Do something and go away (`return`)
Still here? Well if this-other-thing then do something and go away (`return`)
**Still** here? Well if this-other-thing then do something and go away (`return`)
You end up thinking in a series of yes/no tests, and avoid the very human/programmer problem you ran into with nested conditional logic.
In all your other conditions you do a redirect. If the elseif succeeds, but the if does not succeed then you do nothing. It is then trying to render a page using your master template but you have not set any of the variables that it needs. You could fix this by adding another redirect:
if ($last_post->created_at->diffInSeconds() < 15) {
return Redirect::to('/forum/topic/'.$id.'/new')
->withErrors('You really need to slow down with your posting ;)');
}
else
{
return Redirect::to('/somewhere/else/');
}
After discussing this in the Laravel IRC room, we found the solution (and I believe answers here would have sufficed too)
In the end, I came up with this:
$hasposted = ForumPos::where('user_id', '=', Auth::id())->count();
if ($validator->fails()){
return Redirect::to('/forum/topic/'.$id.'/new')
->withErrors($validator->messages());
} elseif ($hasposted != 0) {
$last_post = ForumPos::where('user_id', '=', Auth::id())->orderBy('created_at', 'DESC')->first();
if ($last_post->created_at->diffInSeconds() < 15) {
return Redirect::to('/forum/topic/'.$id.'/new')
->withErrors('You really need to slow down with your posting ;)');
}
}
$new_thread = new ForumThr;
$new_thread->topic = $id;
$new_thread->user_id = Auth::id();
$new_thread->title = Input::get('title');
$new_thread->save();
$new_post = new ForumPos;
$new_post->thread = $new_thread->id;
$new_post->user_id = Auth::id();
$new_post->body = Input::get('body');
$new_post->save();
return Redirect::to('/forum/thread/'.$new_thread->id.'');
If it passes all the if statements, it'll get through to the final request and now I'm happy to say it all works as planned. Thanks, lads!
I inherited code and in the Model the previous developer used the afterFind, but left it open when afterFind is executed in case of many to many relation to that table. So it works fine when getting one element from that Model, but using the relations break it.
public function afterFind($results, $primary = false) {
foreach ($results as $key => $val) {
if (isset($results[$key]['Pitch'])) {
if (isset($results[$key]['Pitch']['expiry_date'])) {
if($results[$key]['Pitch']['expiry_date'] > time()) {
$results[$key]['Pitch']['days_left'] = SOMETHINGHERE;
} else {
$results[$key]['Pitch']['days_left'] = 0;
}
} else {
$results[$key]['Pitch']['days_left'] = 0;
}
To solve this issue I added that code after the 2nd line.
// if (!isset($results[$key]['Pitch']['id'])) {
// return $results;
//
Is there a better way to solve that? I think afterFind is quite dangerous if not used properly.