Search code examples
phplaravel-5php-carbon

How to use Carbon date in where clause in Laravel Collection


I've been struggling for the past quintet of hours to use Carbon date in a where clause on a collection on a Laravel 5.2 project ... without success.

So first I retrieve the collection of booked appointments on a given date from the database :

 $bookedRDVs = RDV::where('idAgenda', $idAgenda)
                    ->whereDate('start', "=", $date)
                    ->get();

Then I have a list of time slots (fixed duration) within the business hours ([businessStart ; businessEnd]) and I want to mark them as free or not.

$currentTime = $businessStart->copy(); // $businessStart is a Carbon date

while($currentTime < $businessEnd){
    echo 'start is an object of class ' . get_class($bookedRDVs->first()->start); // start is an object of class Carbon\Carbon
    echo 'currentTime is an object of class ' . get_class($currentTime); // currentTime is an object of class Carbon\Carbon


    if($bookedRDVs->contains('start', $currentTime) ) {
        $rdv = $bookedRDVs->where('start', $currentTime)->first();
        var_dump($rdv); // prints NULL => why ?
    } //end if
} // end while

Why does $bookedRDVs->contains('start', $currentTime) says true, but then $bookedRDVs->where('start', $currentTime) says null ? 'start' is Carbon object and $currentTime is also Carbon.

While writing this question my mind finally came to whereLoose instead of where. Actually the result of whereLoose is not null anymore (so it may be the solution or there is an issue in my design that I cannot see). Why with the same object class and the same value the where clause is not getting validated ?

Thanks in advance for pointing me to what I am missing here !

Take-away (following Jeremy's answer)

With Laravel prior to 5.3, the where clause in Collection does a strict comparison (whereas the contains does a loose one). That means that the result is true only if the compared "things" are clones of each other. To convince myself I resorted to this example from my code :

 $rdv = $bookedRDVs->whereLoose('start', $currentTime)->first(); 
 echo '</br>' . 'start '; 
 var_dump($rdv->start); // prints object(Carbon\Carbon)#377 (3) { ["date"]=> string(26) "2016-09-13 09:20:00.000000" ["timezone_type"]=> int(3) ["timezone"]=> string(3) "UTC" } 


 echo '</br>' . 'currentTime '; 
 var_dump($currentTime); // prints object(Carbon\Carbon)#278 (3) { ["date"]=> string(26) "2016-09-13 09:20:00.000000" ["timezone_type"]=> int(3) ["timezone"]=> string(3) "UTC" }


 echo '</br>' . ' start clone '; 
 var_dump(clone $rdv->start); // prints object(Carbon\Carbon)#377 (3) { ["date"]=> string(26) "2016-09-13 09:20:00.000000" ["timezone_type"]=> int(3) ["timezone"]=> string(3) "UTC" } 

So the only difference between $rdv->start and $currentTime is the number following the hash sign (see here for details). When those numbers differs it means that the object are not from the same instance. If $rdv->start is cloned then the objects are really identical! Fortunately this has been changed in 5.3 according to the official upgrade doc.


Solution

  • As you have figured out, whereLoose() will work for you. The reason is that method literally calls the where() method, and just passes false as the 3rd argument for strict type checking.

    Per the documentation:

    enter image description here

    With non-strict comparison (what you get with contains() and whereLoose()), it will attempt to juggle the types. More specifically in this case, if the object has a __toString() method (like Carbon does), it can convert it to a string in an attempt to compare.

    With strict comparison, it does not juggle the types and therefore is attempting to compare one object instance directly to another, that although of the same type, have different data.

    With PHP object comparison, strict type checking with === only is true if " object variables are identical if and only if they refer to the same instance of the same class".

    Good question by the way!