I often browse the internet looking for articles of interest, and as I have been a software developer for 4 decades it is quite common for me to look at articles on that subject, especially those featuring my current language of choice which is PHP. I recently came across an article called Removing Static - There and Back Again which I think is giving misleading information and will cause inexperienced programmers to go down the wrong path when it comes to the design and development of database applications using OOP. Before I criticise this article let me first explain why I think I am justified in doing so.
I used several other languages for 2 decades before I switched to PHP in 2002, so I had a lot of experience under my belt, especially when it came to designing and building database applications. Among the lessons I learned along the way were:
I have personally witnessed the mess which can be caused by NOT following these lessons, so excuse me if I continue to follow best practices which in my experience have actually been proven to be best.
After having discovered how easy it was with PHP to deal with HTML forms at the front end and an SQL database at the back end I set out to rebuild my development framework in the new language. I had developed my first framework in COBOL in the 1980s which I rebuilt in UNIFACE in the 1990s, so rebuilding it a third time was no big deal. I had built each of these frameworks with the idea that it would take away the need to write all that repetitive boilerplate code and leave me with nothing but the business rules for each application component.
As well as creating this framework to build database applications I have actually eaten my own dog food and used it to build my own ERP application which, as everyone should know, is a collection of integrated database applications each of which deals with a different business domain. I started building this ERP application with the name TRANSIX in 2007, it went live with its first client in 2008, and in 2014 I joined up with Geoprise Technologies who had used my framework for several years and were more than happy to market my application to the entire world under the new name of GM-X.
My decades of experience in designing and developing database applications should therefore qualify me as being somewhat of an expert in this field. The fact that the current generation of new programmers choose to design their software using a methodology which is different from mine does not automatically make their methodology "right" and mine "wrong", it is merely "different". Any area of complexity can be looked at from more than one angle, and it just happens that I prefer to continue using the approach that has served me well for over 20 years. The fact that younger programmers do not have my depth of experience should instantly tell them that they are operating under a disadvantage, but they continue to blindly follow what they have been taught without realising that their lessons have been incomplete, that what they have been taught is not necessarily the "only" way, or the "right" way, it is just one way among several possibilities.
All the programming languages which I used in the 20th century were procedural whereas the language that I chose for the 21st century (PHP) was object oriented. There are far too many OO programmers out there who seem to think that OO programming and procedural programming require totally different thought processes, but I strongly disagree. As far as I am concerned OO programming is exactly the same as procedural programming except for the addition of encapsulation, inheritance and polymorphism (see What is the difference between Procedural and OO programming? for details). This means that I still design each database application in the same old way - database first and software second - but when it comes to writing the code I try to implement encapsulation, inheritance and polymorphism in order to generate more reusable components. Any programmer worth his salt should tell you that the more reusable components you have then the less code you have to write, which then takes less time to test and debug. As time is money the less time which is spent on writing a piece of software the cheaper and more cost-effective it becomes, and being more cost-effective makes the software more marketable and competitive than a rival piece of software which is slow to produce and therefore more expensive.
The sample code in the article in question clearly demonstrates that he doesn't understand how database applications work. As I explain in Why I don't do Domain Driven Design there are basically two ways of viewing the problem:
By ignoring option (1), which is popular with novice programmers, and instead following option (2) I am able to produce working user transactions using prebuilt templates which provide all the standard boilerplate code and which allow me to add in the business rules later (see The Template Method Pattern as a Framework for details). These templates are built on the fact that when accessing a database table there are only four operations of significance - Create, Read, Update and Delete (CRUD). It does not matter which business entity is being manipulated to carry out which business process, the net result will always be that it ends up performing one or more of those CRUD operations on one or more database tables.
My PHP development framework contains an implementation of the Model-View-Controller design pattern, so each database table has its own Model class. Because every database table, irrespective of its contents, is automatically subject to the same four CRUD operations, I have defined these operations in an abstract table class which is then inherited by every concrete table class. Those business rules which need to be implemented in program code rather than the database design can be inserted into any of the "hook" methods which have been defined in the abstract class.
While the GM-X application currently has over 400 database tables, each with its own Model class, I do not have a separate Controller for each Model. Because each user transaction (or use case) boils down to performing one or more CRUD operations on one or more tables I have created a library of pre-built and reusable Controllers, each of which executes a particular Transaction Pattern, which perform a set of pre-defined operations on an unknown Model (or Models) so all I need to do is supply the name(s) of those Model(s) at runtime using a unique component script. Using this technique I have just 45 different Controllers from which I have built over 3,000 user transactions.
When dealing with currency codes and exchange rates, as shown in Removing Static - There and Back Again, what you are actually dealing with is a Sales Order Processing (SOP) system. This actually requires three separate objects - PRODUCT (what you are selling), CUSTOMER (to whom you are selling) and ORDER (what you actually sell to whom on a particular date). Note that after performing Data Normalisation the number of database tables may actually increase, and in my methodology I treat each table as a separate entity with its own Model class. For example, rather than having a single unit_price on the PRODUCT table I actually have a history of price changes on a separate PRICE_COMPONENT table, each with its own start_date and end_date so that I can maintain different prices on different dates. When performing a lookup on the PRODUCT table I include a lookup on the PRICE_COMPONENT table to retrieve the price which is current at that date.
An ORDER object will also require more than one database table because an order may have any number of order items. This makes "order items" a repeating group which should therefore be split off to a separate table thus giving you an ORDER_HEADER table and an ORDER_ITEM table which exist in a one-to-many relationship where ORDER_HEADER is the parent (one) and ORDER_ITEM is the child (many).
In my first generation of SOP system I dealt with UK organisations which sold only to UK customers, so everything was in UK pounds sterling This meant that there was no need for any currency codes or exchange rates. If any non-UK customer placed an order it was still expressed in UK pounds and the customer was expected to pay in UK pounds.
In the next generation of SOP system the organisation was able to sell to non-UK customers in a currency of their choice. This meant that the order had to maintain two sets of prices - one in the organisation's currency (home currency) and another in the customer's currency (foreign currency). This was achieved quite simply by adding two columns to the ORDER_HEADER table - currency_code and exchange_rate. These new columns were either both blank or both non-blank. All the order values were still held in the database in home currency, but the presence of a currency_code and exchange_rate on the ORDER_HEADER meant that it was easy to switch the displays to show the values in foreign currency simply by multiplying the home currency value by the exchange rate. All online forms had a button which could the amounts displayed between home currency and foreign currency.
Note here an important detail - currency codes and exchange rates have no place on the PRODUCT table as they are only relevant when an order is placed. This is why they have their own columns on the ORDER_HEADER table using values which are obtained from the CURRENCY_CODE and EXCHANGE_RATE tables.
Now that I have outlined my credentials I will begin to tackle what I think is wrong with that article.
$display_value = $home_currency_value * $exchange_rate;
There are other statements in his article which I also find to be very questionable. For example:
An entity with the same ID can return different values on different calls of the same method. It's like my name would be "Tom" in the morning and "John" in the afternoon.
An object which represents a database table does not have an ID other than the primary key of the record which it contains. The value in the name property for that object would therefore be the value in the name column retrieved from the database using whatever arguments were passed in the ->read() method. The value would only change if someone updated it.
Due to a static design of CurrencyProvider, we cannot set currency at the single place of application.
You should never want to set the currency code in a single place in the application. You only set the currency code when you create a sales order using a value which is supplied by the user in the HTML form, so different orders can have different currency codes.
How do I show a price for all the currencies we support?
You don't. The product's price is always that in the price file which is expressed in home currency. A value in a different currency is not relevant until you add that product to a foreign currency order, in which case you can then use the exchange rate for that order date to convert the value.
The idea that you can refactor a code base in one big step using an automated tool is nothing but pie in the sky - it can't be done. A tool can only look at small pieces of code in isolation and suggest possible alternatives. It cannot say that the whole design is wrong, the classes are wrong, the methods are wrong, that the use of encapsulation, inheritance and polymorphism is not as good as it should be. That takes a human eye backed up with human intelligence, and regardless of what anyone says about Artificial Intelligence (AI) computers will be nothing more than fast idiots that can only obey simple commands for many years to come.
There is only one point in that article with which I agree - getting rid of static methods. The use of such methods to set and get the currency code is obviously the brainchild of someone who is trying to prove how clever he is with OO theory, but would never be done by someone who knows how database work and who knows how to write code to put data into and get data out of a database.
When you consider that OO programming means "writing code that is oriented around objects" you should instantly see that using static methods does not fit this description as you never actually instantiate the class into an object before you call one of its methods. If there is no "object" then how can it be "oriented around objects"?
Here endeth the lesson. Don't applaud, just throw money.
Here are some heretical articles I have written on the topic of OOP: