Simple For-Loop results in memory exhausted error - php

After working more than 4 hours on localizing the problem, I finally found the part of my code that leads to the following error:
PHP Fatal error: Allowed memory size of 268435456 bytes exhausted
(tried to allocate 8388608 bytes) in
I'm fetching data from my database. The query returns an array of data with 33 entries (not that much...)
I iterate over that array with a for-loop to get a time difference and flag the array if the difference is too big (simple if-else).... Its quite difficult to explain, but I guess you get what I mean by looking at my code... its pretty simple:
for($i = 0; $i <= count($customerList); $i++) {
$date1 = date_create($customerList[$i]["lastUpdate"]);
$interval = date_diff($date1, $date2);
$min=$interval->format('%i');
$sec=$interval->format('%s');
$hour=$interval->format('%h');
$mon=$interval->format('%m');
$day=$interval->format('%d');
$year=$interval->format('%y');
$date3 = date("H");
if(($year > 0) || ($mon > 0) || ($day > 0)) {
$customerList[$i]["flag"] = 1;
}
else {
if($date3 >= 6 && $date3 <= 21) {
if(($hour > 0) || ($min > 29)) {
$customerList[$i]["flag"] = 1;
}
}
}
}
The variable $date2 is defined before the for-loop with the following line of code:
$date2 = date_create($lastUpdateTime["lastUpdate"]);
Sadly I really don't see how this code can result in that error, since in my opinion this shouldn't really use that much memory...
Im 100 % sure that the error comes from this for-loop, since when I remove it the error is gone. Also, when I place this for-loop inside other views, it results in the same error.
I solved the problem by increasing the maximum memory limit of PHP, still I wonder how this for-loop can cause that problem.
I would really appreciate any explanations, since I don't find anything on the internet...
Oh, I'll just add the query here so you can see that I don't fetch a lot of data (the site handels much bigger queries without any problems...):
SELECT c.id, c.type, c.name, c.environment, cd.customer, MAX(cd.createdAt) AS lastUpdate FROM customerdata AS cd JOIN customers c ON cd.customer = c.id GROUP BY customer ORDER BY c.name ASC
Thank you!

Your for loop condition is $i<=count($customerList) instead of $i<count($customerList). This means your access of $customerList[$i] goes one past the end of the array, creating a new element along the way. Now count($customerList) is one larger, so the loop takes another iteration, ad infinum, thereby using more and more RAM for the growing array.
You should also get a warning message that the element does not (yet) exist when first accessing it, when you read "lastUpdate". Later, when you set the "flag" element, you create the array element.
To make the loop conditions easier to read and prevent typos like this, you could use a foreach loop:
foreach ($customerList as $customer) {
$date1 = date_create(customer["lastUpdate"]);
$interval = date_diff($date1, $date2);
...

Related

Laravel 5.6 custom id with mutator reset counter everyday

I use a mutator to create a custom id for my records to make it look like this:
yyyy-mm-dd-{sequence}
The sequence looks like this
00001
00002
...
So it's 5 digits and is just a counter.
I have 2 problems
1) I don't know how to create a counter in my mutator, I can do a for loop but I don't now how to make an infinte loop that resets when it's tomorrow.
2) I honestly have no idea how to make it reset every day.
My mutator:
public function setFunctionalIdAttribute($id)
{
$date = Carbon::now()->format("Y-m-d");
// I take an extremely large number here because there will never be so much records in 1 day.
for ($counter = 0; $counter <= 100000000000; $counter++) {
$counter = str_pad($counter, 5, '0', STR_PAD_LEFT);
}
$today = Carbon::today();
$tomorrow = Carbon::tomorrow();
if ($today = $tomorrow) {
$counter = 0;
}
$this->attributes['functional_id'] = $date . "-" . $counter;
}
Its hard to say it but, in the nicest possible way, your counter loop doesn't really make any sense, I'm sorry! I'd recommend getting rid of that entirely, or at least read the PHP docs on str_pad.
You also have a conditional statement that checks "is today tomorrow". That to me is a big red flag that the logic, in general, isn't correct.
Let's think through an alternative. You're essentially counting the number of records in a day, to use that as the ID. I'd suggest an approach similar to this:
public function setFunctionalIdAttribute()
{
// 1. Count how many records there are from today
// 2. Make an ID that is this number + 1
// 3. If need be, string pad left with 0's
}
1. Count how many records there are from today
Laravel has a handy whereDate function – from the docs (search for whereDate)
$count = DB::table('users')
->whereDate('created_at', Carbon::today()->toDateString())
->count();
So if we had 3 records made today, $count would be 3.
2. Make an ID that is this number + 1
$count ++;
3. If need be, string pad left with 0's
The PHP docs on str_pad are pretty terrible, lets just cover the basics:
str_pad($input, $length, $pad_string, $pad_type);
$input is the string you are padding
$length is the final length of the string (this is why your for loop was totally unnecessary)
$pad_string if the string length is less than $length, fill up the remaining space with this
$pad_type as you rightly had, is an optional flag to pad left
Your $input is the $count, your $length is 5, judging from your example, $pad_string is "0", and we keep PAD_LEFT.
$id = str_pad($count, 5, "0", PAD_LEFT)
I can't remember how to set an attribute through a mutator so just copying your example (I hope that's correct!) we get:
public function setFunctionalIdAttribute()
{
$count = DB::table('users') // Remember to change this to the correct table name
->whereDate('created_at', Carbon::today()->toDateString())
->count();
$count ++;
$id = str_pad($count, 5, PAD_LEFT)
$this->attributes['functional_id'] = $id;
}
Remember to only do this on create, as we don't want to increment this ID on every save.
I don't know the purpose of your code, but this will allways set "functional_id" to something like "2019-01-23-100000000001", since you are using the $counter variable out of your loop.
Your loop is looping trough without doing anything. (And why the hell are you looping till such a high number if you are not expecting more than 100000 entries?!?)
What you need is the previous counter you have set, ether from DB or somewhere else, but like this your code is not going to work.
In that way you could perform some check like
if ($dateOfLastEntry != Carbon::now()->format('Y-m-d')) {$counter = 1}
otherwise set $counterOfLastEntry + 1
not using that scary for-loop you are using
str_pad performed at the end
Maybe you give us a little more information how this should work, for what you are going to use that counter, and where you are going to store this data.
I know it is a old question, but for everybody that needs something like it for days to come, I made something that resolve the question.
public function Counter()
{
$today = Carbon::today();
$today_files = Shipment::whereDate('created_at', $today)->count();
$counter= $today_files;
if ($today_files != 0) {
$counter++;
} else {
$counter = 1;
}
return counter;
}```

2D PHP array, join values based on similar values

I have PHP array which I use to draw a graph
Json format:
{"y":24.1,"x":"2017-12-04 11:21:25"},
{"y":24.1,"x":"2017-12-04 11:32:25"},
{"y":24.3,"x":"2017-12-04 11:33:30"},
{"y":24.1,"x":"2017-12-04 11:34:25"},
{"y":24.2,"x":"2017-12-04 11:35:35"},.........
{"y":26.2,"x":"2017-12-04 11:36:35"}, ->goes up for about a minute
{"y":26.3,"x":"2017-12-04 11:37:35"},.........
{"y":24.1,"x":"2017-12-04 11:38:25"},
{"y":24.3,"x":"2017-12-04 11:39:30"}
y=is temperature and x value is date time,
as you can see temperature doesn't change so often even if, it change only for max 0.4. But sometimes after a long period of similar values it change for more than 0.4.
I would like to join those similar values, so graph would not have 200k of similar values but only those that are "important".
I would need an advice, how to make or which algorithm would be perfect to create optimized array like i would like.
perfect output:
{"y":24.1,"x":"2017-12-04 11:21:25"},.........
{"y":24.1,"x":"2017-12-04 11:34:25"},
{"y":24.2,"x":"2017-12-04 11:35:35"},.........
{"y":26.2,"x":"2017-12-04 11:36:35"}, ->goes up for about a minute
{"y":26.3,"x":"2017-12-04 11:37:35"},.........
{"y":24.1,"x":"2017-12-04 11:38:25"}
Any help?
As you specified php I'm going to assume you can handle this on the output side.
Basically, you want logic like "if the absolute value of the temperature exceeds the last temperature by so much, or the time is greater than the last time by x minutes, then let's output a point on the graph". If that's the case you can get the result by the following:
$temps = array(); //your data in the question
$temp = 0;
$time = 0;
$time_max = 120; //two minutes
$temp_important = .4; //max you'll tolerate
$output = [];
foreach($temps as $point){
if(strtotime($point['x']) - $time > $time_max || abs($point['y'] - $temp) >= $temp_important){
// add it to output
$output[] = $point;
}
//update our data points
if(strtotime($point['x']) - $time > $time_max){
$time = strtotime($point['x']);
}
if(abs($point['y'] - $temp) >= $temp_important){
$temp = $point['y'];
}
}
// and out we go..
echo json_encode($output);
Hmm, that's not exactly what you're asking for, as if the temp spiked in a short time and then went down immediately, you'd need to change your logic - but think of it in terms of requirements.
If you're RECEIVING data on the output side I'd write something in javascript to store these points in/out and use the same logic. You might need to buffer 2-3 points to make your decision. Your logic here is performing an important task so you'd want to encapsulate it and make sure you could specify the parameters easily.

While Loop, Maximum execution time of 30 seconds exceeded

I need to call the script function open() when the counter exceeds 5, but when I load my webpage, it shows me a fatal error : Maximum execution time of 30 seconds exceeded. Is that my while loop problem?
$result = mysqli_query($dbconnect, "SELECT id, temp, hum, lum FROM weather ORDER BY id DESC LIMIT 5");
while($row = mysqli_fetch_assoc($result)){
$weather["temp"] = $row["temp"];
$weather["lum"] = $row["lum"];
$tempcounter = 0;
while ($weather["temp"] > 27) {
$tempcounter++;
}
if($tempcounter > 5){
echo"<script>open()</script>";
}}
Your while loop is infinite. The loop will start if $weather["temp"] is bigger than 27 and inside you increment the tempcounter but never change $weather["temp"] so it will always be bigger than 27 and never get out of the loop.
You could put $weather["temp"] in a variable. Then inside the loop increment it also. But most importantly you need to reverse your condition. If you increment you need to check < and if you decrement you can check >.
If you increment:
$weatherTemp = $weather["temp"];
while ($weatherTemp < 27) {
$weatherTemp++;
}
If you decrement:
$weatherTemp = $weather["temp"];
while ($weatherTemp > 27) {
$weatherTemp--;
}
And then you can declare $tempcouter like this:
$tempcounter = $weatherTemp - $weather["temp"]; //Temperature needed to get to 27
if($tempcounter > 5){
echo"<script>open()</script>";
}
Code is not tested it might not work has it is.
If what you want to do is get the difference between 27 and your temperature you are not using the right logic here. You could simply take 27 minus your temperature form the query. If the result is negative just change it to positive and you get the difference.
I think it is time over problem.
You can fix this problem by 2 methods.
Set max_execution_time to large value ex. 300 or larger
Begin of your php script call set_time_limit function
set_time_limit(0);
In this loop:
$tempcounter = 0;
while ($weather["temp"] > 27) {
$tempcounter++;
}
You're not updating $weather["temp"], so it will loop forever if that condition is satisfied.

Limit the number of results in a for loop in reverse order

I created a for loop to occupy a table on my site. Originally I limited the number of results by using if (i++ <= 10) {.
However, I adjusted the for loop so it is reverse order (most recent item first). Here is my code:
for ($i=count($shipmentInfoArray['ShipmentList'])-1; $i >= 0; $i--) {
$shipmentUrl = BASE_URL . "shipment.php?ShipmentID=" . $shipmentInfoArray['ShipmentList'][$i]['ShipmentID'];
Obviously, since it's counting down instead of up, my original limit code won't work.
I tried this, but it didn't work:
if ($i-- >= (count($shipmentInfoArray['ShipmentList']) - (count($shipmentInfoArray['ShipmentList'])-10)) {
My hope was it would take the total number of entries, then subtract the same number by the number I want to display (100 - (100-10) = 10 entries.
I also tried adding a 'break' at the end, assuming that this didn't work because the "count" wasn't finished yet. However that didn't work either :/
Any suggestions? Thanks guys!
I feel you're making this over-complicated, and confusing yourself as you try to reason through the resulting complexity. There are many approaches, I don't know that one is any better than another...
You could initialize a second counter (say j) to 0 and increment it on each iteration, as one simple solution.
Something like the following should work:
// Set the limit to the max number of results
$LIMIT = 10;
foreach (array_reverse($shipmentInfoArray['ShipmentList']) as
$shipmentIndex => $shipment) {
if ($shipmentIndex >= $LIMIT) {
break;
}
// Use $shipment
}

PHP - Select data from where timestamp is

I have a piece of code which will select x number of days into the future and print it out.
I'm then trying to select data from those timestamps, and print out accordingly:
Below I am selecting the number of days in the future it should loop ($max) and how many rows/data there is ($data["cnt"])
$stmt=$dbh->prepare("select round((expire - unix_timestamp()) / 86400) as days, count(*) as cnt from xeon_users_rented WHERE user_by=:username group by days;");
$stmt->bindParam(":username",$userdata['username']);
$stmt->execute();
$data=$stmt->fetchAll();
$max = max(array_map(function($d){
return $d['days'];
}, $data));
$expireData = array();
I then loop through x number of days in the future and print it out: (Let's say that $x is = 10)
for($x = 0; $x <= $max; $x++){
if ($data[$x]["cnt"] > 0){
$expireData[] = $data[$x]["cnt"];
}else{
$expireData[] = 0;
}
$stamp = strtotime('+'.$x.' day', time());
$expireDays[] = date("Y/m/d", $stamp);
}
I then print out the days data and the data:
<?php echo implode("', '", $expireDays); ?>
<?php echo implode("', '", $expireData); ?>
Which gives me:
'2014/11/05', '2014/11/06', '2014/11/07', '2014/11/08', '2014/11/09', '2014/11/10', '2014/11/11', '2014/11/12', '2014/11/13', '2014/11/14', '2014/11/15'
and (where each digit represent a value for that specific date)
2,8,0,0,0,0,0,0,0,0
So far so good. The only problem here is, that the data (2,8,0,0etc.) is not correct. It should for example be:
0,0,2,0,0,0,8,0,0,0
My question is: How can I print out the data, where it matches the timestamp (xeon_users_rented.expire)?
To simplify my answer from before and to answer your question directly "How can I print out the data, where it matches the timestamp"?
You need to first put the unix timestamp of "strtotime('+'.$x.' day', time());" into an array from your original loop. Remove the expiredays[] stuff from that loop.
Then loop through that array and then use array_search for finding any matching indexes in the $data array.
if (found in $data array)
$expireDays[] = $data[array_search position]['cnt'];
else
$expireDays[] = 0;
From what I have gathered in what you are trying to establish, the sql query (for example) returns an array such as:
$data = array(
array("days"=>232975857, "cnt"=> 4),
array("days"=>232975867, "cnt"=> 10),
array("days"=>232976689, "cnt"=> 0),
array("days"=>232976688, "cnt"=> 2)
);
The max in your case is 10. However, please note that your code (below):
$max = max(array_map(function($d){
return $d['days'];
}, $data));
could return a lot of PHP E_NOTICE errors and be slow because you are working out a maximum from the unix_timestamp at that stage which is for example 232975867 (far too many loops I suspect that you need). The max should be worked out in the following way I suspect:
$max = count($data);
In my case (from my data example example) this will return something like 4 for which your for loop code will need to reference "<" not "<=". To optimise this I would actually put straight into the for loop "...; $x < count($data); ...;" unless of course you need the $max later.
Here is a big issue for me. I don't see where currently you have any correlation between the $stamp variable and the "days" column from your sql statement. Perhaps I have not seen enough information from you to fully understand or I am interpreting your question incorrectly but your sql for one will not necessarily return the same dates as the stamp variable will calculate and will not certainly return a cnt of 0 for any dates that do not exist in that table. This is why:
if ($data[$x]["cnt"] > 0){
part of the section is unnecessary and possibly incorrect.
To answer your question why do you get "2,8,0,0,0,0...." instead of the order you expected is because the sql does not return 0 cnt values as quite simply the date does not exist in it's table and your implode still returns '0's appended as you forcibly added the 0's afterwords with the line:
$expireData[] = 0;
First of all, you need to fill your data (or re-write your data to contain cnt of '0's in the correct array places). You can achieve this from the sql level with a subquery that ensures missing dates are contained and then a cnt value of 0 is enforced. I'll leave this to you to work out however another way (via PHP) is to do the following (pseudo code):
place each 'stamps' (in unix format) in array from previous loop
forloop $i through $stamps
array_search $stamps against $data['days']
if (found)
$expireDays[] = $data[array_search position]['cnt'];
else
$expireDays[] = 0;
This means that you remove the $expireDays from your first loop.
Finally, perhaps I have misunderstood and that your 'days' column shouldn't match your $stamp dates as those dates are in the future and your 'days' columns are in the past. In this case, your only option is to adjust your sql statement to forcibly include dates that are missing (between a certain date range)
Good luck.

Categories