Enumerate all items to find those that match a complex condition
You can't reduce code you wrote because each condition has to be checked and you're already using variables to clarify your intent. What you can do is to move all that code out of your loop, reading that code in future you won't need to scroll over all those ifs:

foreach ($workingTimes as $hour =&gt; $minutes) {
    foreach ($minutes as $shift) {
        if (!canBookRoom($hour, $shift))
            continue;

        $formatted = sprintf("%s-%s", $hour, $shift);
        $timetable[$formatted] = 'Bookable';
    }
}

...

function canBookRoom($hour, $minute) {
    if (!isThereAvailableRoom($hour, $minute))
        return false;

    return areThereAvailableResources($hour, $minute);
}

function isThereAvailableRoom($hour, $minute) {
    if (!$this-&gt;isBookable($hours, $shift))
        return false;

    return $this-&gt;checkRooms($hour, $shift);
}

function areThereAvailableResources($hour, $minute) {
    if (!$this-&gt;checkEmployees($hour, $shift))
        return false;

    return $this-&gt;checkResources($hour, $shift);
}


What has been done


Reading your loop you're not really interested in all details about availability, there you just need to know if it's bookable or not. Pulling out a function for that makes your code easier to parse and your intent explicit. If/when you'll be interested in that details you may browse to canBookRoom() function.
canBookRoom() function itself explicitly split booking availability in two logic domains: room availability and resources availability. Each one has its own function.
areThereAvailableResources() unifies resources, from a higher level point of view both humans and materials are resources you can count.
Your local variables has been dropped, if each function name is self descriptive then you don't need them.


What should be done


Your loop is still doing many things: it searches for a bookable room inside a time range and if it finds an available time then it marks that range as bookable.


At least something should be done in a separate function. Here you have two options for your hypothetical buildTimetableForAllBookableRooms() function: refactor out function to fill timetable or add one function findAllBookableRooms() which returns all available time frames when there is a bookable room plus another function formatTimetableForBookableRoom() to perform booking, called in a foreach loop. 

function findAllBookableRooms() {
    foreach ($workingTimes as $hour =&gt; $minutes) {
        foreach ($minutes as $shift) {
            if (canBookRoom($hour, $shift))
                yield sprintf("%s-%s", $hour, $shift);
        }
    }
}

function formatTimetableForBookableRoom() {
    foreach (findAllBookableRooms() as $bookableTimeFrame) {
        $timetable[$bookableTimeFrame] = 'Bookable';
    }
}


Here I used generator syntax but you may also return an array. Note that findAllBookableRooms() may also return unformatted value using yeld array($hour, $shift);. 


Wherever it will be your $formatted variable has a poor name. It should describe what it contains otherwise you may simply drop it).
Another thing to do (but I don't have enough context) is to rename isBookable() and checkRooms() functions, from name alone it's not clear what they're actually doing and why/how they're different. Note I renamed isBookabe() to isBookable(), I suppose it's just a typo.