It would be helpful to have more scenarios. Instead of us trying to think them up.
But let's take yours, and expand on it.
the restaurant has a finite number of tables, and each table can only be booked for say 2 hours slots during the restaurants opening times...
a question could be:
what kind of check needs to be made before say you book a table for 2 at 6PM.
so lets start with tdd and figure out how this might work, starting with a feature test.
test: a_table_for_cannot_be_booked_at_when_there_are_no_tables_available
/** given all tables are booked */
//what's a booked table? humm?
//this means a table needs to know if it's booked.
//$table->booked() ??
/** when you attempt to make a reservation */
//how do you reserve a reservation?
//maybe reservation()->make($numberOfPlaces, $reservationTime) ??
// it it checks for tables for the Timeslot that can accommodate the number of parties requested.
// crud, whats' a Timeslot?
//perhaps it returns true of false??
/** then, available tables should equal 0. */
//what is a available table ?
//this means a table needs to know if it's available?
//$table->available() ?
so, with this simple test idea, we know that:
- You've got lots of tables each with a size
- a Table knows a bit about itself. booked and available.
- There's a thing called a Timeslot
- You have many Reservations with associated Tables
** a reservation hasManyTables
** a reservation hasManyTimeSlots
And the major check point is in the Reservation class. it's the decider! But what does it decide? And what does it need to decide?
- for starters, is there a Timeslot?
So where are the rules? i'd say close to Reservation::make() where make checks a number things, except in all likelyhood Reservation is also a model. (Is it doing too much stuff) Remember the first S in Solid (a class should only be responsible for one thing, or there should be only one reason to change it.
But, humm, where do we go from here?
Well how about a flushing out. Reservation with a unit test.
Reservation:tests
test: a_reservation_cannot_be_made_without_a_timeslot
/** given there are no available Timeslots */
$timeslots = create('Timeslot', [availeble => false], 2); //what is available??
/** when we attempt to make a reservation */
$reservation->make($people, $time)
/** then the attempt to make the reservation it should throw a ReservationNotAvailable Exception <-- or something like that
$this->assertException('NoTimeSlotAvalible') //this should be above reservation->make()
Well now we have to face the Timeslot problem/question.
More, perhaps tables don't need to know about themselves as much.
So let's take a look at timeslot with a test
Timeslot Unit Test:
test: you_can_check_for_available_timeslots //we need to come back to this one
test: a_timeslot_knows_if_it_it_is_available
//given we have a timeslot
//and the timeslot is used //what's used mean?
//then a timeslot is not available
test: a_time_slot_knows_it_is_used_for_a_given_time()
//this means we must mark somehow that a timeslot is used. also do we need times?
test: a_time_slot_can_be_marked_as_used.
test: timeslots_can_know_if_there_are_timeslots_unused_for_a_given_time.
test: timeslots_must_have_a_table
test: tables must have a seating property
well this is getting super long.
but what have we learned so far:
Reservations actually depend on timeslots.
Timeslots are really just time and table based
Timeslots depend on tables.
Tables have capacity
The rules are a bit more spread out then we anticipated (different objects need to know about various states). But the checking of rules doesn't need to be spread out. as in:
-
it must check for timeslots for time and capacity
-
there are likely a set number of timeslots
-
timeslots likely have a given time attribute. //humm, is Time a thing?
-
timeslots must check for tables for capacity
-
tables have capacity attributes (and likely a booked time)
And most likely place to check for availablity starts in reservation->make()
- it check Timeslots for availablity
- Timeslots checks for unbooked tables for a given time and table capacity.
- Tables can should know availability by capacity
if all these rules pass, then Reservation::make() can return true or what not.
and of course it updates properties on Timeslots, and tables.
Now, where do place the various Business Rules.
- a lot of people say a rule should not be on the Model. but it could be.
- you could have a Rules collection of sorts which depend on the models attributes.
I think depending on the complexity you could pick one or the other.
But again, in my humble opinion Reservation is the place to run them.