How business logic is handled within an application and where to place the logic will depend on the type of business logic . The decision on where to place the code which enforces the Business Rule will be determined by the guiding principle of Separation of Concerns and the desire to make Classes which are "loosely coupled".
Consider the following simple business rule for our tennis club. Junior members pay a reduced fee of two euros per hour while senior members pay five euros per hour. It's likely that this would need to be accompanied by rules limiting the times of the day when the Junior members are allowed to pre-book courts but for now we'll just stick to the simple rule relating to automatically calculating the booking fee.
One way to look at this rule is to say that it is domain logic as opposed to application logic. Rules which allow one data attribute to be derived in a calculation from another could be considered "domain logic". A person's age, for example, can be calculated from their date of birth. It's reasonable and useful for us to place the code for that calculation in the model for the Member as it is information which is "concerned" with the member. Also all the information required to make a that calculation is within the Member class so there is no need for any "coupling" of classes.
Given that the cost of booking a court per hour is likely to change over time it would be a mistake to hard-code this into the business logic. Instead we should give it its own database table and model class. Run the following SQL script on your database to create the membershiptype table.
create table membershiptype ( membershiptype varchar(6), courthourlyfee decimal(18,3), created_at timestamp null, updated_at timestamp null, deleted_at timestamp null, primary key(membershiptype) ); insert into membershiptype(membershiptype, courthourlyfee) values('junior', 2.0); insert into membershiptype(membershiptype, courthourlyfee) values('senior', 5.0); alter table member add foreign key(membertype) references membershiptype(membershiptype);
Based on this Junior members will have to pay €2 per hour and Seniors will have to pay €5 per hour. Now we need to scaffold our new table into a Model class. To do this type the following command into the CLI
php artisan infyom:scaffold membershiptype --fromTable --tableName=membershiptype
When we scaffold this table, the scaffolder picked up on the one to many relationship between member and membershiptype from the foreign key reference and created a hasMany() releationhip within the model class. We now need to create the inverse relationship within the member class. This is a "belongsTo" relationship indicating that every member belongs to one membership type. This will allow us to connect member objects directly to membershiptype objects(without doing a SQL join) allowing us to correctly identify the courthourlyfee for each member. To do this add the following function to \App\models\member.php
public function membershiptype()
{
return $this->belongsTo(\App\Models\membershiptype::class,'membertype','membershiptype');
}
Under our new setup, in order to know how much a logged-in user should pay per hour for court bookings we will need to use their membershiptype to lookup the hourlyFee in the membershiptype table. The following line of code allows us to "walk the object tree" to obtain relevant information in related table. To make this connection using SQL you would have to perform a query with an inner join - this is far easier.
$costPerHour = $user->member->membershiptype->courthourlyfee;
If you have followed the posts in the security section and you still have a loggedInUser route which will show you the logged in users details, you can add the above line to the getLoggedInUserDetails function in the memberController in order to see if you can correctly identify the courtHourlyFee.
In the next post we'll use this line of code to automatically calculate the cost of the booking before we save the data to the database.