I have been using PHP as my primary language now for over 20 years, starting with version 4, and by making optimal use of its OO capabilities I have single-handedly produced a multi-award winning framework called RADICORE which has been used as the foundation of a multi-module ERP package called GM-X Application Suite. Because I learned so much from online tutorials I felt the need to give back to the community by contributing articles of my own to document how I went about designing and building my framework. While the internet is full of valuable resources, because there is no barrier as to who can post what, there is also a surprising amount of bullsh*t and mis-information. This is my reaction to one of those articles.
Some while ago I came across Back to Basics ... Three or Four OOP Pillars? where the opening statement was:
A New Addition to the OOP Pillar Family? -- Data Abstraction
I disagree with this statement as there is no fourth pillar to OOP, there are only three - Encapsulation, Inheritance and Polymorphism. I say this because when you are identifying the characteristics of a new programming paradigm you should only mention those things which are unique to that paradigm, those things which set it aside from previous paradigms. Before switching to PHP I spent 20 years with other non-OO languages, including 16 years with COBOL which was the most widely used procedural language of its day. I therefore feel that I am more than qualified to comment on the differences between procedural and object oriented languages.
When I began my foray into OOP the first thing I did was look for a description as a starting point. The best description I found went as follows:
Object Oriented Programming is programming which is oriented around objects, thus taking advantage of Encapsulation, Inheritance and Polymorphism to increase code reuse and decrease code maintenance.
That is why I have concentrated my work around those three factors, and only those three factors. Anything which does not contribute to the production of more reusable code which requires less maintenance I consign to the rubbish bin (or flush down the toilet).
As far as I am concerned abstraction is not one of the defining characteristics of OOP as it existed as a mental process long before OOP came into being.
The PHP manual did not mention this thing called "abstraction", so I was unaware of its significance for many years. Then I started coming across articles which claimed that abstraction was the fourth pillar of OOP, but I could find no adequate description of why this was so. It appeared that far too many clueless newbies were confused over the true meaning of encapsulation, which had to be debunked in articles such as Abstraction, Encapsulation, and Information Hiding by Edward V. Berard of The Object Agency. Abstraction and encapsulation do not mean the same thing, there are entirely different. The only tenuous connection is that the result of performing an abstraction may be an abstract class which holds code which can be reused/shared by any number of concrete classes. Encapsulation can be demonstrated in code, but as abstraction is a mental process it cannot. This is why I say that that are not the same.
The only worthwhile definition of abstraction I have ever found is contained within Designing Reusable Classes which was published in 1988 by Ralph E. Johnson & Brian Foote. Although I did not discover the existence of this document until many years after I had written my framework, which was due to the fact that very few other people had provided references to this article, I was surprised to see that I had intuitively been implementing this technique which they describe as programming-by-difference. This requires that after you have written the code for several entities you examine that code looking for similarities and differences. You then find a way to put the similarities into a reusable module and confine the differences into a series of unique modules. As I develop nothing but database applications where each table is a separate entity I use an abstract table class for the similarities which is then inherited by every concrete table class which contains nothing but the differences. I have written more on this topic in The meaning of "abstraction".
Every entity in the business layer of a database application is a table, and every database developer should know that the only operations which can be performed on a database table are Create, Read, Update and Delete (CRUD). It does not matter that each table holds totally different data, each table is subject to exactly the same operations. I have created methods for each of these operations and placed then into an abstract class, as shown in Common Table Methods below:
Methods called externally | Methods called internally | UML diagram |
---|---|---|
$object->insertRecord($_POST) | $fieldarray = $this->pre_insertRecord($fieldarray); if (empty($this->errors) { $fieldarray = $this->validateInsert($fieldarray); } if (empty($this->errors) { $fieldarray = $this->commonValidation($fieldarray); } if (empty($this->errors) { $fieldarray = $this->dml_insertRecord($fieldarray); $fieldarray = $this->post_insertRecord($fieldarray); } |
ADD1 Pattern |
$object->updateRecord($_POST) | $fieldarray = $this->pre_updateRecord(fieldarray); if (empty($this->errors) { $fieldarray = $this->validateUpdate($fieldarray); } if (empty($this->errors) { $fieldarray = $this->commonValidation($fieldarray); } if (empty($this->errors) { $fieldarray = $this->dml_updateRecord($fieldarray); $fieldarray = $this->post_updateRecord($fieldarray); } |
UPDATE1 Pattern |
$object->deleteRecord($_POST) | $fieldarray = $this->pre_deleteRecord(fieldarray); if (empty($this->errors) { $fieldarray = $this->validateDelete($fieldarray); } if (empty($this->errors) { $fieldarray = $this->dml_deleteRecord($fieldarray); $fieldarray = $this->post_deleteRecord($fieldarray); } |
DELETE1 Pattern |
$object->getData($where) | $where = $this->pre_getData($where); $fieldarray = $this->dml_getData($where); $fieldarray = $this->post_getData($fieldarray); |
ENQUIRE1 Pattern |
Here the methods called externally are the ones which are called from the Controller while the methods called internally are called only from within the abstract table class which is inherited by every Model. Each external method then acts as a wrapper for a group of internal methods. Notice that before and after each database operation, which has the "dml_
" prefix, there are pairs of "pre_
" and "post_
" methods. These will contain calls to "hook" methods to enable each subclass to provide custom logic.
More information can be found in A Development Infrastructure for PHP.
This is where the clueless newbies fall short as they cannot think of any properties which are common to all database tables. It is quite obvious that the application data held in the table's columns is unique, so what data is possibly shared? The answer is the metadata which is stored in the INFORMATION_SCHEMA within the database itself. To cater for this data I have the following common table properties within the abstract table class.
$this->dbname | This value is defined in the class constructor. This allows the application to access tables in more than one database. It is standard practice in the RADICORE framework to have a separate database for each subsystem. |
$this->tablename | This value is defined in the class constructor. |
$this->fieldspec | The identifies the columns (fields) which exist in this table and their specifications (type, size, etc). |
$this->primary_key | This identifies the column(s) which form the primary key. Note that this may be a compound key with more than one column. Although some modern databases allow it, it is standard practice within the RADICORE framework to disallow changes to the primary key. This is why surrogate or technical keys were invented. |
$this->unique_keys | A table may have zero or more additional unique keys. These are also known as candidate keys as they could be considered as candidates for the role of primary key. Unlike the primary key these candidate keys may contain nullable columns and their values may be changed at runtime. |
$this->parent_relations | This has a separate entry for each table which is the parent in a parent-child relationship with this table. This also maps foreign keys on this table to the primary key of the parent table. This array can have zero or more entries. |
$this->child_relations | This has a separate entry for each table which is the child in a parent-child relationship with this table. This also maps the primary key on this table to the foreign key of the child table. This array can have zero or more entries. |
$this->fieldarray | This holds all application data, usually the contents of the $_POST array. It can either be an associative array for a single row or an indexed array of associative arrays for multiple rows. This removes the restriction of only being able to deal with one row at a time, and only being able to deal with the columns for a single table. This also avoids the need to have separate getters and setters for each individual column as this would promote tight coupling which is supposed to be a Bad Thing ™. |
These placeholders are empty within the abstract superclass, but when a concrete subclass is instantiated they are loaded with values from a separate <tablename>.dict.inc file which is exported from the Data Dictionary.
The erroneous definition I found for abstraction in Back to Basics ... Three or Four OOP Pillars? goes like this:
Data Abstraction - the Encapsulation of State in Data Transfer Objects Using Composites and Aggregates DTOs
This is wrong for the following reasons:
To make matters even worse the clueless newbie even has a wrong definition for encapsulation:
Encapsulation - Details Required to Return a Result from a Class Method should be Hidden from the Calling Method
What on earth made this clueless newbie come up with THAT crazy definition? As explained here the TRUE definition is as follows:
The act of placing data and the operations that perform on that data in the same class. The class then becomes the 'capsule' or container for the data and operations. This binds together the data and functions that manipulate the data.
As mentioned earlier, the defining characteristics of OOP can only include those things which did not exist before the advent of OOP. This means that the following statements are invalid:
How can I possibly say such a thing? Because calling a method on an object in an OO program is exactly the same as calling a function/subroutine in a procedural program. I spent 16 years programming in COBOL, so I was used to writing statements such as:
CALL <procedure> USING <input-argument> GIVING <output-argument>
Each of these arguments is a record, also known as a composite data type or a struct in the C programming language.
The following things are blindingly obvious to me:
As these features existed way before OOP came into being it is my opinion that they cannot be described as defining characteristics of OOP. They were not created especially for OOP, instead they can be said to be inherited by OOP.
It is articles like Back to Basics ... Three or Four OOP Pillars?, written by clueless newbies, which fill the internet with mis-information which, instead of adding to the pool of knowledge, end up by muddying the waters and leading new programmers up the wrong path. Instead of producing rock star programmers they are producing rocks-in-the-head programmers. This fills me with despair. All I can do is attack this mountain of mis-information with logic, but it appears that far too many people have been brainwashed into following dogmatism instead of pragmatism that they are immune to logical arguments.
Here endeth the lesson. Don't applaud, just throw money.