内存在嵌套循环中使用数组耗尽

This is the error I'm getting.

Fatal Error: Allowed memory size exhausted.

I'm fetching arrays containing a date from and a date till. I'm trying to get all the dates in between and add them to a new array. Apparently nested loops and multi-arrays are exhausting.

I need a less exhausting way of getting all the dates.

This is my code:

$query = "SELECT *
          FROM reservate
          ORDER BY from";
$result = $connect->query($query);

$reservations = array();
if ($result->num_rows > 0) {
    while($row = $result->fetch_assoc()) {
        $reservations[] = $row;
    }
}

$connect->close();

//AVAILABILITY CHECK
$nonAvailable = array();
foreach($reservations as $reservation){
    $from = $reservation['from'];
    $till = $reservation['till'];

    while($from <= $till){
        $nonAvailable[] = $from;
        $from = date('Y-m-d', strtotime("+1 days"));
    }
}

It looks like you made an infinite loop1.

// if $till is in the future, this is an infinite loop
while($from <= $till){
    // appending the same value of $from on each iteration
    $nonAvailable[] = $from;
    // as $from never changes value
    $from = date('Y-m-d', strtotime("+1 days"));
}

Add 1 day to the current value of $from

while ($from <= $till){
     $nonAvailable[] = $from;
     // add 1 day to $from
     $from = date('Y-m-d', strtotime($from, "+1 days"));
}

There are still a few improvements that can be made:

  • In the following example the entire table is not copied to the reservation array and we are only retrieving the columns we need from the table.
  • Comparing integers instead of strings.
  • Adding 1 day to $from manually rather then relying on strtotime to do it.
$query = "SELECT from, till
          FROM reservate
          ORDER BY from";
$result = $connect->query($query);

$nonAvailable = array();

if ($result->num_rows > 0) {
    while ($row = $result->fetch_assoc()) {
        $from = strtotime($row['from']);
        $till = strtotime($row['till']);

        while ($from <= $till) {
            $nonAvailable[] = date('Y-m-d', $from);
            $from += 60*60*24;
        }
    }
}

$connect->close();

It is likely that this algorithm can be made much more efficient. For example do you need an exhaustive list of each day something is reserved? If not then just the start date and end date should suffice.


1 Given enough time and memory the loop would exit as strtotime("+1 days") would eventually return a value greater than $till.

An alternative way of doing this, using just a single query.

This takes your reservation table and cross joins it with a couple of sub queries that get ranges of numbers. Each get the numbers 0 to 9, and one is used as units and one as tens. Combined together they give you the numbers 0 to 99 (so 99 day range).

The WHERE clause just checks that the from date (van) plus the number of days (from 0 to 99) is less than or equal to the to date (tot). Hence a room (I am assuming multiple rooms, or some other item that is being booked out) is put out with each date where it is booked:-

SELECT room, DATE_ADD(van, INTERVAL (tens.i * 10 + units.i) DAY) AS non_available
FROM reservate
CROSS JOIN (SELECT 0 AS i UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) units
CROSS JOIN (SELECT 0 AS i UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) tens
WHERE DATE_ADD(van, INTERVAL (tens.i * 10 + units.i) DAY) <= tot
ORDER BY room, non_available

SQL fiddle example:-

http://www.sqlfiddle.com/#!9/83054/1

You could use a table of integers instead of the 2 sub queries.