The examples on this site are currently tested to work on Phalcon V3.4 and Phalcon Devtools V3.2 Some issues may arise when using later versions.

Please get in touch or post a comment below the post if you encounter a problem.

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(30),
	courthourlyfee	decimal(18,3),
	primary key(membershiptype)
);

insert into membershiptype values('Junior', 2.0);
insert into membershiptype values('Senior', 5.0);

alter table member 
add foreign key(membertype) references membershiptype(membershiptype);
Now scaffold the membershiptype table
phalcon scaffold membershiptype --get-set --ns-models=tennisClub

next delete the file /app/models/Member.php and re-generate it using the following command. Take care to copy any additional code you have written for the Member class to a temporary location for backup.

phalcon model member --force --get-set --namespace=tennisClub

Theoretically we should be able to keep the original Member.php file and the --force option should preserve any changes already made to the Member model but re-generate the important aspects of the code. Unfortunately, while this will pick up on many database changes, it doesn't pick up on the addition of a new index or key. Deleting the file and re-generating the code will get the scaffolder to pick up on the foreign key but you may have to replace other code that you have included in the model such as a getAge() or a __toString() function. This following line should now have been added to the initialize function of the class by the scaffolder.


The belongsTo() function creates a one-to-many association between membershiptype and member which allow us to "walk the object tree" when calculating what each member should pay.

Now edit the file /app/models/Booking.php to modify the booking model class. Add the following function which will calculate the cost of the booking based on the number of hours of the booking and the membershiptype of the person booking.

public function beforeSave()
{
    $costPerHour = $this->getMember()->getMembershiptype()->getCourthourlyfee();
    $startTime = \DateTime::createFromFormat("H:i",$this->starttime);
    $endTime = \DateTime::createFromFormat("H:i",$this->endtime);
    $interval = $endTime->diff($startTime);
    $totalHours = round($interval->s / 3600 + $interval->i / 60 + $interval->h, 2);
    $this->fee = $costPerHour * $totalHours;	
}

*The code in red is an example of "walking the object tree"

The beforeSave() function allows the programmer to perform calculations in advance of persisting the data. The getMember() and getMembershiptype() functions are magic methods of a sort which rely on the alias set in the initialize functions. The word "get" is prepended to the alias to create a virtual method which will return the member object for that booking. This, being an object, can in turn have the getMembershiptype() magic method called on it to return a Membershiptype object which can use the getters of that object to return the necessary information. In this way we are "walking the object tree". This approach, when you get used to it, is far simpler and requires less code than performing SQL joins in order to get related information from another table.

After this, the DateTime class is used to calculate the interval between the starttime and endtime and the $totalHours is calculated to express the interval as a number of hours with a decimal point where necessary. This can then be used to calculate the cost of the booking.

Placing this code into the Booking class does introduce a level of dependency or "coupling" between the Booking object and the Member object but as long as that relationship exists anyway there shouldn't be a problem. I'm open to discussion on this so please feel free to comment below if you disagree with this approach. It has the advantage of keeping the Business Logic in the Model classes. One alternative would be to place this code in the Controller class. Many MVC purists might object to this as the Controller's job is to manage those interactions between the user and the view.

I imagine many programmers who are comfortable with frameworks such as Angular and React would prefer to encode a business rule such as this into the front end using the sophisticated techniques afforded by those frameworks. What's important here is that he programmer has a good understanding of the issues and implications of these coding decisions and can back up their decisions with reasoned argument.