In the 40+ years that I have been programming I have used various languages, which include COBOL, UNIFACE and PHP. The first two were compiled and statically typed while the last is interpreted and dynamically typed. It should be obvious that each of these languages is different, which means that moving from one to another involves dealing with the differences in order to become proficient in that new language. No competent programmer should ever complain about the differences, nor should they attempt to continue using their old programming style when it conflicts with the style dictated by the new language. Anybody who does deserves a smack on the back of the head and then sent to bed without any supper.
First, a brief description of the two terms:
PHP is not a language which is compiled, it is interpreted. This means that it was designed from the ground up to be dynamically typed, so any type checking is performed at run time. Functions are provided to test a variable's type. Explicit type conversions can be performed manually using Type Casting, but implicit type conversions are usually performed automatically as described in Type Juggling.
In spite of this I am continually amazed at the number of programmers who, after switching to PHP from a strictly typed language, constantly complain that this causes no end of type errors. They cannot wrap their heads around how PHP works and insist that all dynamically typed languages are inherently bad without understanding how type safety is handled. The fault does not lie with PHP, it lies with the inability of these junior-grade, second-rate, intellectually-challenged programmers who fail to RTFM (Read the Frickn' Manual) and understand how PHP's type system actually works. You have to understand how it works before you can make it work for you. The alternative is to ignore how it is supposed to work and then wonder why you keep getting type errors. If you understand the circumstances under which type errors can occur, such as with input from HTML documents, you should be able to write code to deal with those circumstances. All type errors are the result of badly written code. If you write code that produces a type error which you can fix by adjusting the code, it means that you made a mistake by not including that adjustment in the first place.
I regard strict typing as a crutch for the mentally deficient. It is like the training wheels on a bicycle - they are OK when you are a child as they stop you from falling over and hurting yourself, but when you are an adult they simply get in the way. To say that statically-typed languages are better and more popular than dynamically-typed languages is not supported by the fact that 60% of the world's programming languages support dynamic typing and only 40% support static typing. This would indicate to me that those who support static typing are in the minority, and that the newer languages are more likely to support dynamic typing.
A lot of poorly educated people seem to think that type safety requires strict/strong typing, but this is not correct. I found the following definition in this wikipedia article:
In computer science, type safety and type soundness are the extent to which a programming language discourages or prevents type errors. Type safety is sometimes alternatively considered to be a property of facilities of a computer language; that is, some facilities are type-safe and their usage will not result in type errors, while other facilities in the same language may be type-unsafe and a program using them may encounter type errors. The behaviors classified as type errors by a given programming language are usually those that result from attempts to perform operations on values that are not of the appropriate data type, e.g., adding a string to an integer when there's no definition on how to handle this case. This classification is partly based on opinion.
Type enforcement can be static, catching potential errors at compile time, or dynamic, associating type information with values at run-time and consulting them as needed to detect imminent errors, or a combination of both. Dynamic type enforcement often allows programs to run that would be invalid under static enforcement.
Note that a type safe language discourages or prevents type errors. It either reports them or attempts to fix them, but does not ignore them. The article goes on to say the following:
If a type system is sound, then expressions accepted by that type system must evaluate to a value of the appropriate type (rather than produce a value of some other, unrelated type or crash with a type error).
...
The semantics of a language must have the following two properties to be considered type-sound:Progress
A well-typed program never gets "stuck": every expression is either already a value or can be reduced towards a value in some well-defined way. In other words, the program never gets into an undefined state where no further transitions are possible.Preservation (or subject reduction)
After each evaluation step, the type of each expression remains the same (that is, its type is preserved).
Note where under the heading Progress it says every expression is either already a value or can be reduced towards a value in some well-defined way.
Every language that recognises different data types must provide the ability to convert a value from one type to another, and only fail if the conversion is not possible. As explained in Type Casting (from wikipedia) this conversion can be either explicit (manual) or implicit (automatic). Languages with strong typing typically do little implicit conversion and discourage the reinterpretation of representations, while languages with weak typing perform many implicit conversions between data types. The C language is one of several languages which can perform implicit conversions, and as PHP was written to provide wrappers for C functions that same behaviour was inherited by PHP. This means that strlen('123')
will return 3, exactly like strlen(123)
. Similarly sqrt('9')
will also return 3, exactly like sqrt(9)
. A TypeError will only be thrown if the value cannot be converted, such as attempting to convert the string '10 green bottles'
into an integer.
This means that PHP satisfies both of the Progress and Preservation statements in the wikipedia definition, therefore PHP can be described as being Type Sound. A language does not have to be strictly typed in order to be type sound.
Because all early languages were compiled type safety was achieved in the following ways:
This meant that each data item had to be defined before it could be used. In COBOL it was necessary to define variables in the DATA DIVISION before they could be referenced in the PROCEDURE DIVISION. In UNIFACE all structures had to be defined in the Application Model as entities, and each component had to reference one or more of these entities before being compiled. As the definition of each variable included its data type the compiler could ensure that each data item could never be overwritten with a value of the wrong type, nor could it be used as a function argument of a different type.
When dealing with a compound data structure no type checking could be performed on the structure as a whole, it could only be performed when individual elements within that structure were referenced. It was up to the programmer to ensure that the definition of the structure being received was a perfect match with the physical structure being sent. This was because the structure was sent as a single block, and it was not possible to perform any type checking on the individual components within that block. If any elements in the structure of the receiving program were not perfectly aligned with those in the sending program then the values obtained from that structure would be unpredictable. This would not produce a type error at run time as type checking is only performed at compile time. It could, however, cause a data corruption which could go undetected for some time.
The fan boys of static typing are still fixated on the way that the early primitive languages behaved by sticking to the following statement:
It is wrong to pass an argument to a function which is not of the expected type
However, they fail to realise that modern languages have become more sophisticated and developer-friendly with the passing of time. As explained above, when a type mismatch is detected in a type safe language then it can do one of two things - it can either fail with a TypeError, thus forcing the programmer to fix the code by performing an explicit type conversion, or it can perform an implicit conversion and only fail if the value cannot be converted. As explained in RFC: Strict and weak parameter type checking it is considered to be more efficient and developer-friendly to perform any type checking and implicit conversions within the function which receives an argument rather than forcing an explicit conversion to be duplicated in every place which provides that argument. This then allows the above rule to be changed to something which is less strict but still effective:
It is wrong to pass an argument to a function which is not of the accepted type
Notice that the word expected has been changed to accepted. While a function may expect an argument of type X a modern sophisticated language which is Type Safe will accept an argument of type Y provided that the value can be converted internally from type Y to type X.
PHP's ability to perform automatic implicit conversions is known as Type Juggling. Manual conversions are described in the manual under the heading of Type Casting. The ability to perform implicit type conversions is important in PHP for the simple reason that all data which is provided from external sources, such as the user interface (which uses HTML) or a database (which uses SQL), is never provided with the correct data types. This is not a limitation in PHP, it is a limitation which is built into those external protocols. All input from an HTTP request, such as from a $_GET or a $POST, is provided in an array of strings. All data returned from an SQL query is also returned in an array of strings. It has never been necessary to insert code to convert any string value into the correct type as PHP has always performed those conversions implicitly behind the scenes. This is the reason why many developers are totally unaware of PHP's internal type system, so those moving to PHP from a strictly typed language don't understand how PHP's type system works and therefore regard the lack of strict typing as a source of errors. The simple fact is that all type mismatches are caused by programmer mistakes, but where a strictly typed language will fail immediately a dynamically typed language will attempt to correct that mistake by performing an implicit conversion and only produce a TypeError if that conversion fails.
Note that implicit conversions of data which has been retrieved from the database should never fail as each column's value is already guaranteed to match that column's data type as defined in the database schema. If it did not then the insert or update operation would have failed.
Static typing's origins lie in the early days of programming languages, primarily driven by a desire for type safety and compile-time error detection. A language does not have to be strictly typed in order to be type safe, nor does it have to be compiled as type errors can be detected and handled at run time. PHP is interpreted, not compiled, and it handles all type errors in a type safe manner.
Some of the following statements are taken from Type Wars which was published by Robert C. Martin (Uncle Bob) in May 2016.
I first ran into the concept of types in 1966 while learning Fortran as a teenager. In Fortran there were essentially two types. Fixed Point (integer), and Floating point. Expressions in Fortran could not "mix modes". You could not have integers alongside floating point numbers. Only certain library functions could translate between the modes.
This was because the binary representation of integers and floating point numbers was completely different, so if you tried to use a series of bits as one type of number when in fact it was something else then the results were unpredictable and sometimes catastrophic. The only way to avoid such errors was to give each variable a type and not allow a value of the wrong type to be loaded into it. This also meant that if a function expected an argument of a particular type it would perform the relevant type checking at compile time and generate an error if there was a mismatch.
C, of course, had types; but they were not enforced in any way. You could declare a function to take an int, but then pass it a float or a char or a double. The language didn't care. It would happily push the passed argument on the stack, and then call the function. The function would happily pop its arguments off the stack, under the assumption that they were the declared type. If you got the types wrong, you got a crash. Simple. Any assembly language programmer would instantly understand and avoid this.
At least it had the sense to crash instead of returning an unreliable result. But what if it had the ability to detect that a variable was of the wrong type, and had the ability to convert a value from one type to another, how much effort would it take to automatically convert it to the correct type? Surely that would be better than crashing?
In the article Polymorphism and Inheritance are Independent of Each Other I found the following statements regarding static type checking:
The original compiled languages (C++, etc) performed static type checking because of performance issues.
...
C++ was dominant until the mid 1990s simply because it was an object oriented solution that was NOT interpreted. This meant that on the slow CPUs of the time it had decent performance. We used C++ because we could not get comparable performance with any of the interpreted object-oriented languages of the time, i.e. Smalltalk.
...
It was only during the time frame when Java and C# were introduced that CPU power was sufficient for interpreted languages to give sufficient performance at run time. The transition from having polymorphism and inheritance tightly coupled to being more loosely coupled depended on run time interpreters being able to execute practical applications with decent performance.
...
The benefits of pure polymorphism outweigh any advantage that compile time type checking provides, especially when we have access to very sophisticated debuggers and support for runtime exception handling. In general, I believe that the benefits of pure polymorphism outweigh the value of static type checking.
This tells me that some programmers were beginning to realise that static type checking was starting to become more of a problem than a solution.
The next problem came when reading and writing data from files. I personally have worked with flat files, indexed files, hierarchical database and network databases, and they all had the same characteristics:
Here are the most common data types which are supported by programming languages and database systems:
byte | Stores whole numbers from -128 to 127 |
short | Stores whole numbers from -32,768 to 32,767 |
int | Stores whole numbers from -2,147,483,648 to 2,147,483,647 |
long | Stores whole numbers from -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 |
float | Stores fractional numbers. Sufficient for storing 6 to 7 decimal digits |
double | Stores fractional numbers. Sufficient for storing 15 to 16 decimal digits |
boolean | Stores true or false values |
char | Stores a single character/letter |
These are all known as scalar data types or primitive data types as they only hold a single value which can therefore be given a single type. It is possible in PHP to create collections of scalars in objects and arrays, but neither of these can be given their own "type".
As far as I am concerned the term "type" includes only those scalar data types which are directly supported by the database and/or the programming language. While some languages support the concept of classes and objects as user defined types this is not the case with PHP. Neither classes nor objects can be given a "type" as they are nothing more than containers for collections of other variables, and each collection may contain a variety of scalars with different types. If you examine an object using the gettype() function the result will always be "object". An object can contain any number of values, known as properties, with a mixture of types.
Similarly arrays are also containers for collections of variables, so they cannot be given their own type. If you examine an array using the gettype() function the result will always be "array". An array can contain any number of values with a mixture of types.
Everybody knows that you can inherit from one class, which then becomes a superclass, to create a new class which is known as a subclass. In those strictly typed languages where a class can be given a type the superclass becomes a supertype and a subclass becomes its subtype. Those languages then require that you cannot reference an instance without specifying its type otherwise polymorphism will not work. This is explained in Polymorphism and Inheritance are Independent of Each Other. This completely artificial restriction does not exist in PHP as classes do not have types, so all that is required for polymorphism to work is that the same method signature exists in multiple objects. When you call a method on an object PHP does not waste time checking that the method exists in the object's supertype, it just checks that the method exists within the object itself. The sharing of common methods is mostly achieved in the RADICORE framework by having every one of my concrete table class inherit from the same abstract table class, but also in the separate Data Access Objects (DAO) where the various method names are manually duplicated instead of being inherited.
When accessing data from external sources, such as from a compiled form or a disk/database file, this data was presented as a fixed length record (also known as a struct, composite data type or data buffer) which contained a number of fields each with its own data type and length.
Before PHP came along earlier languages, such as COBOL, were strictly typed for the simple reason that they used predefined records into which or from which all I/O operations were performed. All input/output operations required a pre-defined and pre-compiled record structure which identified precisely the type and size of every piece of data that was passed in that operation. The entire structure is passed as a single argument. Here is an example:
01 customer-record. 05 cust-key PIC X(10). 05 cust-name. 10 cust-first-name PIC X(30). 10 cust-last-name PIC X(30). 05 cust-dob. 10 cust-dob-year PIC 9(4). 10 cust-dob-mm PIC 99. 10 cust-dob-dd PIC 99. 05 cust-balance PIC 9(7)V99.
The PIC(TURE) clause identifies the data type:
Numeric values were uncompressed by default, meaning that PIC (9)
(the same as PIC (9) USAGE DISPLAY
) would require 9 bytes. Several compressed formats were allowed, such as:
In the early days of computing when hardware was expensive and programmers were cheap it was necessary to store numbers in as small a space as possible, which is why the binary format was introduced.
Note that it is imperative that the receiving structure matches the sending structure both in size and composition otherwise the receiving subprogram or subroutine may not be able to see the correct values. If the sending buffer is larger it will cause a buffer overflow, if the receiving buffer is larger it will cause an underflow.
PHP does not support pre-defined and pre-compiled data structures. Data which comes into PHP from external sources, such as HTML or SQL, cannot be presented in pre-defined structures as neither of those protocols support structures. Instead the data is presented in arrays whose contents are not pre-defined and static, they are dynamic and built at run time.
PHP is a language which is not compiled, and it does not use pre-defined and fixed data structures. It therefore does not meet the requirements of all other statically typed languages as explained in the introduction. PHP is interpreted. It cannot perform any type checking when the source code is parsed into bytecode (the equivalent of being compiled) as a variable's type can be changed at any time. For this reason all type checking on a variable can only be performed at run time when that variable is used in an expression. It is not necessary to define a variable and its type before you assign a value to that variable. Once a value has been assigned it is also possible to overwrite that value with a different value of a different type. This means that the following statements will not cause an error:
<?php $foo = 27; // value is an integer $foo = 'twenty seven'; // value is a string ?>
While the latest version of PHP supports optional type declarations for function arguments, return values and class properties, all this does is prevent a variable from being assigned a value of the wrong type, as shown in the following code snippet:.
<?php class Foobar { var int $foo; // this variable can only hold values of type 'int' (integer) function __construct () { $this->foo = 27; // value is an integer $this->foo = 'twenty seven'; // value is NOT an integer, will cause a TypeError } ?>
It is important to note that in PHP type declarations cannot be used for values which are supplied from external sources, such as the following:
Instead of pre-defined and fixed data structures PHP uses dynamic arrays for all HTML and SQL values. They are dynamic for the simple reason that you cannot declare what elements an array should contain before you receive it from an external source. You can also add or remove elements from an array at any time as there is no way you can prevent these operations. This also means that when reading an array you may need to verify its contents before you start processing what may not be there.
You might also think that this would force the programmer to take an array containing string values and insert lots of code to manually cast each string value into the correct type, but this is not the case. If you read the section on Type Juggling in the PHP manual you will see where it says the following:
PHP does not require explicit type definition in variable declaration. In this case, the type of a variable is determined by the value it stores.
...
PHP may attempt to convert the type of a value to another automatically in certain contexts. The different contexts which exist are:
- Numeric
- String
- Logical
- Integral and string
- Comparative
- Function
Note: When a value needs to be interpreted as a different type, the value itself does not change types.
This was reinforced in RFC: Strict and weak parameter type checking where it said the following:
PHP's type system was designed from the ground up so that scalars auto-convert depending on the context. That feature became an inherent property of the language.
It goes on to say:
Strict type checking is an alien concept to PHP. It goes against PHP's type system by making the implementation detail become much more of a front-stage actor.
In addition, strict type checking puts the burden of validating input on the callers of an API, instead of the API itself. Since typically functions are designed so that they're called numerous times - requiring the user to do necessary conversions on the input before calling the function is counterintuitive and inefficient. It makes much more sense, and it's also much more efficient - to move the conversions to be the responsibility of the called function instead. It's also more likely that the author of the function, the one choosing to use scalar type hints in the first place - would be more knowledgeable about PHP's types than those using his API.
Finally, strict type checking is inconsistent with the way internal (C-based) functions typically behave. For example, strlen(123) returns 3, exactly like strlen('123'). sqrt('9') also return 3, exactly like sqrt(9). Why would userland functions (PHP-based) behave any different?
Proponents of strict type hinting often argue that input coming from end users (forms) should be filtered and sanitized anyway, and that this makes for a great opportunity to do necessary type conversions.
While both HTML input values and SQL query results are presented as strings there are the following differences:
While I agree with the statement input coming from end users (forms) should be filtered
I disagree with the statement this makes for a great opportunity to do necessary type conversions
for the simple reason that it is not "necessary" to manually convert each input string into the correct type. All that is "necessary", as explained above, is that each input string be filtered so that it can be automatically cast into the correct type without causing a TypeError. Adding code to do manually what the language was designed to do automatically does not strike me as the act of a competent programmer, someone who understands what the word "efficient" actually means. In the above-mentioned RFC it says the following:
For example, consider a function getUserById() that expects an integer value. With strict type hinting, if you feed it with $id, which happens to hold a piece of data from the database with the string value "42", it will be rejected. With auto-converting type hinting, PHP will determine that $id is a string that has an integer format - and it is therefore suitable to be fed into getUserById(). It will then convert the value it to an integer, and pass it on to getUserById(). That means that getUserById() can rely that it will always get its input as an integer - but the caller will still have the luxury of sending non-integer but integer-formatted input to it.
The key advantages of the proposed solutions are that there's less burden on those calling APIs (fail only when really necessary). It should be noted that most of the time coding is spent consuming existing API's and not creating new ones. Furthermore it's consistent with the rest of PHP in the sense that most of PHP does not care about exact matching zval types, and perhaps most importantly - it does not require everyone to become intimately familiar with PHP's type system.
This makes sense to me - if a function is called in a thousand places then it is more efficient, as well as less error prone, to perform any type checking just once within the function itself instead of duplicating it in those thousand places. If the language can auto-convert values into the correct type then why should I waste my time in adding code to do the same thing manually?
As I have said earlier, static typing is only truly practicable in a compiled language in which variables have first to be defined with a data type before they can be assigned a value. As the compiler then knows the type of each variable it can then prevent that variable from being overwritten with a value of the wrong type. When it comes to data being obtained from external sources, such as from the user interface or the database, the data is not presented one variable at a time, it is presented in a compound data structure where a particular group of variables is defined in a single contiguous block or buffer. Each individual item of data within this block has to be defined with the correct type and size. It is therefore vitally important that the structure used to receive data is exactly the same as that used to send the data.
In compiled languages the layout of each screen/form had to be defined and compiled before it could be used. This included both the layout on the screen and the areas on the screen used to display or enter data. Special software was needed to complete this task:
The structures of the database tables were handled differently:
In COBOL all forms handling was carried out using the VPLUS Forms Management System using the compiled forms which were were held inside a file created using the FORMSPEC program. Each application program had to use the VPLUS intrinsics (system functions) to open the forms file, identify which form to use, then call the VGETBUFFER and VPUTBUFFER intrinsics to transfer data between the terminal and the program. A single program could switch between several different forms while it was being executed, but could only handle one form at a time.
In UNIFACE you ran a forms component which had the structure of a single form built into it. Each was built using data from the Application Model, so it was linked to specific tables and columns within the application database. Each form component therefore "knew" the data type for each column and could prevent any column from being overwritten with data of the wrong type.
In COBOL all database activity was performed by calling specialised TurboImage intrinsics which required a separate function for each operation. Among the function arguments were the following:
In UNIFACE each form component contained links to entities (tables) in the Application Model. When the RETRIEVE button was pressed the form component would cause the built-in database driver to generate and execute an SQL "select" query. Because of its connection to the Application Model all data could be returned in fields of the correct type. Note that the generated query could only reference a single database table, so JOINS to other tables were impossible. If the STORE button was pressed it would generate and execute the appropriate SQL "insert", "update" or "delete" query.
The problem with structures is that every form and every database table deals with a different set of data items, therefore requires a structure which is uniquely tailored for that set of data items. That structure must have an entry for each data item which defines its type and size, and these items must also be defined in the same sequence as expected by that form or database table. If there is any mismatch the compiler will not be able to detect it and will not be able to generate a TypeError. It is only when referencing a single data item, not a group, that any type checking can be performed.
In my early days as a junior programmer each of these data structures was constructed by hand by each programmer, and this was the source of a large number of errors. Because the discrepancy was not noticed at compile time it only appeared at run time, but never in the form of a TypeError. If a data item within that structure was defined as being a 9-digit binary number then at runtime the language would treat that item as a 9-digit binary number. If, however, the receiving buffer did not match what was actually sent when the value which was used could actually be something completely different and unexpected. This could result in a data corruption somewhere else in the code that may not be spotted for some time. It may even take a lot of effort to track down the source of such an error. I should know as I have had to track down a few of those errors myself.
When I eventually became team leader in another company I found a way to solve this problem. By reading the database and forms handling manuals I discovered that both of them provided library routines which identified the data structures which were required by each table or form, so I wrote a program called COPYGEN which extracted this information and wrote out the details to a disk file in the format of a Copy Library. Structures in the copy library could then be imported into a program using the COPY statement. This meant that each time a form or table was amended all that was necessary to keep all the programs properly synchronised was to rerun the COPYGEN program and compile all the affected programs. By automating this procedure I eliminated the source of a large number of manual mistakes.
Each programming language has its own way of doing things, its own set of advantages and disadvantages, and it is up to the programmer to make the best use of the facilities which the language has to offer in order to produce cost-effective software. In my long career I have use three different languages, and with each change I have seen my levels of productivity increase dramatically. Because I have only developed database applications for the enterprise which have required the services of Role Based Access Control (RBAC), I have developed an RBAC system as part of a development framework in each of those languages. As these applications contain sets of user transactions (tasks) to view and maintain the contents of different database tables I have used different techniques to create a typical family of forms for a single database table. The time taken to create these forms/tasks for a single table were as follows:
Part of this increase in development speed has be down to the architecture used by each language:
PHP does not use pre-compiled and static data structures for HTML and SQL data for the simple reason that it is not compiled and neither of those protocols support static data structures. This is not a limitation of PHP, it is a limitation of those two protocols. Instead the data is always presented in the form of a dynamic array in which all the values are strings. It is not necessary to define the structure of an array before it is filled with data as PHP provides methods to examine that structure after it has been created. This provides much more opportunity for reusable code as it is possible to write a single function to deal with data which is contained within an array without knowing which HTML form or SQL query provided that data. If I were to use separate data structures then I would have to write a separate function to deal with each of those structures. Static data structures are nowhere near as reusable as a dynamic array.
When PHP receives an HTTP request it has no knowledge of the structure of the HTML document which generated it, it simply receives the request data in either the $_GET or $_POST array. That array can then be passed as-is as an argument on a method call on an object, and it is up to that object to deal with the contents of that array. This allows me to have a single reusable Controller to perform an INSERT operation on a Model (database table), and that single Controller can pass ANY data to ANY Model. It is then up to the business rules within each Model to decide if that data is valid for that operation so that it can complete that operation. If I were to use a static data structure instead of a dynamic array then I would be forced to use a separate Controller to deal with each separate structure. This would also mean that none of those Controllers would be reusable which is the exact opposite of what object oriented programming is supposed to provide.
Accessing the database using SQL queries does not require static structures, either for reading or writing, as the entire query is nothing more than a single string. This means I can send the component parts of a query to a database object in the form of an array, and PHP's array-to-string capabilities means that it is a simple process to generate a query string from that input data. When reading from the database it does not matter how many columns are returned, they can all be contained in a single array variable whose structure does not have to be pre-defined. While some junior programmers still produce a separate Data Access Object (DAO) for each table the RADICORE framework contains a single DAO for each supported DBMS which can handle any query, whether simple or complex, for any table.
PHP also offers advanced code sharing using inheritance. The RADICORE framework uses inheritance properly by only inheriting from an abstract table class from which all concrete table classes (the Model in MVC) are built. The fact that every Model class inherits the same common table methods means that any object which calls these methods can be used, via polymorphism, with any Model. I specifically designed each Controller to perform its actions on an unknown Model where the identity of the Model class is defined in a small component script and passed down to the Controller using a form of dependency injection. This means that instead of having a purpose-built Controller for each Model I can use a set of pre-built and reusable Controllers, one for each of my Transaction Patterns. The contents of the $_GET and $_POST arrays can be changed at any time without having to make changes to either the Controller or the Model.
Note that common table methods pass the application data around in a dynamic array which means that they can pass around the data for any database table without knowing what the array contains. A standard function within the framework is used to validate that the contents of the array is valid for that Model. This also means, for example, that I can extract all the application data from a Model using a single call to the getFieldArray() method. The contents of this array can then be used in the standard View object to transform that data into the format required by the user.
The abstract class also contains a set of common table properties which are filled with data in each Model's constructor when it is instantiated into an object. It is this data which effectively configures the abstract class for use with a particular database table. Note that the Model classes do not have to be constructed by hand as they can be generated from the Data Dictionary which is populated from information extracted from the INFORMATION_SCHEMA within the application database.
The paper Designing Reusable Classes, which was published in 1988 by Ralph E. Johnson & Brian Foote, states that a major motivation for object-oriented programming is software reuse
which means that the more reusable code which you have at your disposal then the less code you will have to write and the more productive you will become. My RADICORE framework is able to provide an enormous amount of reusability because of its use of dynamic arrays in an abstract class.
As far as I am concerned there are no problems with arrays, only opportunities. While there are some programmers who think that PHP arrays are bad I totally disagree. My ill-informed colleagues should stop wasting time in complaining about them and instead learn how to use them properly. Once I learned how they worked I found ways to use them so as to maximise the amount of reusable software at my disposal, and the volume of reusable components I have in my framework is greater than anyone else's. As a devout follower of the KISS principle I refuse to spend any time in looking for ways to avoid using arrays as the alternatives are always more complicated than they need be.
PHP obtains data from external sources using two different protocols - HTML for the user interface and SQL for the data store. Unlike earlier alternatives which I encountered in my previous languages neither of these protocols use pre-defined and compiled data structures, they use dynamic structures which start off as a list of strings and which are converted into an associative array of strings. All the PHP code has to do is the equivalent of saying give me your data and put it into this array
and hey presto! the data appears. I do not have to define the structure of the array before it is filled with data as PHP will accept whatever data is sent. A single row of data appears as an associative array of name=value
pairs. Multiple rows appear as an indexed array of associative arrays where the index number represents the row number.
When I began to build my prototype application I started by creating a separate class to model the properties and operations for each database table as this mirrored the architecture used in UNIFACE which had a separate entity for each table in its Application Model. This automatically produced a 2-tier architecture as I needed a separate component to load and instantiate each Model class so that it could then control which methods were called on that object in order to satisfy the user's request. This became the Controller component in my implementation of the MVC design pattern.
When creating an HTML document, which is one large string of text, all the values are strings. When an HTML document is filled in by the user and sent to the server all the values are strings which appear in either the $_GET array or the $_POST array. While learning how to use PHP I looked at code samples which I found either on the internet or read in books, and I noticed that they all created classes which had a separate class property for each piece of data. This meant that they had to unpick the array into it different elements so that each element could then be loaded into the class property. I did not like this for two reasons:
After building the Models and Controllers for the maintenance of two database tables I noticed a great deal of duplicated code, so I looked for ways to reduce it to a bare minimum. My solution involved two simple steps:
The abstract table class became the heart of my development framework with its common table methods and common table properties. This immediately replaced all that tight coupling with loose coupling which, as you should be able to see in the code sample, means that the Controller can be asked to call its methods on any Model in the application. The use of an abstract class also enabled me to implement the Template Method Pattern so that the standard code used by the framework can easily be supplemented with custom code by utilising any of the "hook" methods which are available in any concrete class. Template methods are a fundamental technique for code reuse
, and they have contributed to the huge levels of reusability in my framework.
When creating an SQL query, which is also one long string of text, which is sent to the database, all the values are strings. When data is retrieved from the database the query will return its result in an array where each row of data appears as an associative array of of name=value
pairs. Multiple rows appear as an indexed array of associative arrays where the index number represents the row number.
Some junior-grade ill-informed wannabe programmers think that because in PHP all data from external sources appears in arrays where none of the values have the "correct" type it must mean that it is not Type Safe. These clueless individuals should learn to read the following:
If a type system is sound, then expressions accepted by that type system must evaluate to a value of the appropriate type (rather than produce a value of some other, unrelated type or crash with a type error). A well-typed program never gets "stuck": every expression is either already a value or can be reduced towards a value in some well-defined way.This means that, in a dynamically typed language where all type checking is performed at run time, if a type mismatch is detected then the program can EITHER fail immediately with a TypeError OR attempt to convert the value to the expected type and only produce an error if the conversion fails.
PHP may attempt to convert the type of a value to another automatically in certain contexts.
This means that PHP adheres to the definition of a type safe language. It is perfectly capable of taking any value which is a string and auto-converting it to a different type depending on the context. That is what the manual says, and that is the behaviour which I have come to rely on since I started programming with PHP 4 way back in 2002.
Note that I have never used Type Hinting (where Type Juggling still takes place) or Strict Typing (which results in a TypeError). These features did not exist in PHP 4 when I developed my framework, and as I didn't have a problem with type errors I saw no reason to change my code to introduce a solution which I didn't need.
Any string value obtained from the database should automatically convert to the expected type without any sort of error for the simple reason that if it was invalid then the database would not have allowed it to have been written in the first place.
However, all user input, especially that which is obtained from an HTML document, should be considered as being suspect and should be validated before being processed and written to the database. This is because it is possible for the user to enter a non-numeric character into a field that should only contain a number. But where should this validation be performed, and how should it be performed? It would not be wise to leave it up to the database to reject any invalid data as that would be far too late. Any error with a database query should cause the program to abort, and that will be very inconvenient for the user. The time to detect and reject invalid data is BEFORE the database update is executed, not AFTER, which means that, at a bare minimum, the validation rules performed by the database should be duplicated in the code. If any errors are detected the update should be terminated and a suitable error message should be sent back to the user so they can correct the error and try the operation again.
Every programmer should know that it is possible to obtain the specifications (name, data type, size, et cetera) of all the columns in a database table by querying the INFORMATION SCHEMA. Most junior-grade programmers will then use this information to manually insert the equivalent checks into their code, but as that is a manual process it is slow, laborious and prone to error. Being more skillful than your average programmer I decided to automate this process by creating an updated version of the COPYGEN program which I wrote in COBOL in the 1980s. This would generate structures which could added to a Copy Library which could then be imported into a program at compile time using the COPY command.
PHP does not provide these facilities, so I had to create my own. I started off by creating a group of common table properties within my abstract class to hold this metadata. Instead of a single COPYGEN program I created an entire subsystem called a Data Dictionary which contained components to extract data from the DBMS and import it into the dictionary database, then export from this database to produce a separate table structure file, one for each database table, in the file system. There is an intermediate EDIT phase which allows this information to be customised before it is exported.
The most important part of this data is the $fieldspec array which contains the specifications for every field/column within that particular database table. It should not take a genius to realise that if I have two arrays - one called $fieldarray which contains user input in the form of a collection of name=value
pairs, and a second called $fieldspec which contains a collection of name=specification
entries - it would not take much effort to write a routine that iterates through these two arrays so that it can compare each field's value with its specifications. This is now implemented as a standard validation object which is called automatically by the framework during the execution of the relevant common table method. If this detects any mismatch between a field's value and its specifications it will generate a suitable error message, jump out of the current INSERT or UPDATE operation and return control to the user, thus giving them the opportunity to correct the error before retrying the operation.
Note that it is NOT necessary to convert any values from strings to their "proper" types as PHP's Type Jugging capabilities will automatically coerce any value into the expected type depending on the context. It would be a violation of YAGNI to insert code to do something manually which will already be done automatically.
Having said that PHP was designed from the outset to be dynamically typed there are a large number programmers of limited ability out there who do not appear to have the mental capacity to deal with dynamic typing. They switch to a language which is different, then have the audacity to complain that it is different! They learned to code using a statically type language, have always been taught that static typing is "best", and, like a pack of trained monkeys, they refuse to consider any alternatives. They cannot deviate from what they have been taught. I spent the first 20 years of my programming career using strictly typed languages, but as soon as I started playing with PHP I took to dynamic typing as a duck takes to water. This is unlike my intellectually inhibited colleagues who turn up their noses as if it were a turd in a toilet.
To these intellectual lightweights I can only say one thing: If you don't like dynamic typing then for f***s sake stop whining and stop using a dynamically typed language
. To do otherwise would be as stupid as a non-swimmer jumping into the deep end of a swimming pool while wearing concrete boots and then complaining that he's drowning. Get real, you numpties. It's time to grow up, put on your big-boy pants and learn to act as adults.
What is even worse is the fact that some of these people have infiltrated the team of core developers and are gradually converting the language (I call this sabotaging) to be strictly typed. First there was the introduction of Type Hinting in PHP 5, as a result of PHP RFC: Scalar Type Declarations, which later was promoted to Type Enforcement with the declare(strict_types=1); directive. The use of strict type checking was supposed to be entirely optional, but the implementation of PHP RFC: Deprecate passing null to non-nullable arguments of internal functions in version 8.1 introduced a serious backwards compatibility (BC) break and a stupid inconsistency. This RFC started with the following statement:
Internal functions (defined by PHP or PHP extensions) currently silently accept null values for non-nullable arguments in coercive typing mode. This is contrary to the behavior of user-defined functions, which only accept null for nullable arguments. This RFC aims to resolve this inconsistency.
The statement Internal functions currently silently accept null values for non-nullable arguments in coercive typing mode
is correct even though in the manual none of the function signatures were explicitly marked as being nullable. This is irrelevant as this situation was covered in the manual where it said:
Converting to string
String conversion is automatically done in the scope of an expression where a string is needed.
null is always converted to an empty string.
Converting to integer
To explicitly convert a value to int, use either the (int) or (integer) casts. However, in most cases the cast is not needed, since a value will be automatically converted if an operator, function or control structure requires an int argument.
null is always converted to zero (0).
The statement This is contrary to the behavior of user-defined functions, which only accept null for nullable arguments
assumes that no user-defined function could accept null for any argument, but this was changed several years before in version 7.1 with the introduction of nullable types. Nullable type syntactic sugar was added with the following description:
A single base type declaration can be marked nullable by prefixing the type with a question mark (?). Thus ?T and T|null are identical.
This meant that when you looked at the function signatures in the manual and did not see that an argument was explicitly marked as nullable with this Nullable type syntactic sugar then a novice programmer could assume that none of these arguments would accept null. This assumption would be incorrect for the reasons stated above - (a) accepting nulls was standard behaviour for the language since its inception, and (b) silently accepting nulls was documented as standard behaviour in the manual. Note that with all the changes made to the language to incorporate Type Hinting none of the signatures for internal functions was ever updated in the manual, hence the two became out of step. This glaring mistake told me straight away that the numpties who both proposed and voted for this RFC had failed to Read The Frickn' Manual (RTFM). Had they done so they would have realised that they had two choices:
One of these would produce a massive BC break while the other would not.
They made the WRONG choice, which is why I wrote The PHP core developers are lazy, incompetent idiots. Not only was it the wrong choice, it contradicted the following statements made in PHP RFC: Scalar Type Declarations where it said:
Behaviour of weak type checks
A weakly type-checked call to an extension or built-in PHP function has exactly the same behaviour as it did in previous PHP versions.
Backward Incompatible Changes
Since the strict type-checking mode is off by default and must be explicitly used, it does not break backwards-compatibility.
Unaffected PHP Functionality
When the strict type-checking mode isn't in use (which is the default), function calls to built-in and extension PHP functions behave identically to previous PHP versions.
If strict typing is turned OFF (the default setting) then the PHP manual states the following:
Strict Typing
By default, PHP will coerce values of the wrong type into the expected scalar type declaration if possible.
Warning: Function calls from within internal functions will not be affected by the strict_types declaration.
All of the above statements are wrong for the simple reason that all internal functions will throw a TypeError if you try to pass null for any argument. This means that you now have to insert code to do manually what the language previously did automatically. This requires using the relevant type casting on an argument, such as:
$result = function((string)$string); $result = function((int)$integer); $result = function((float)$float);
Without this manual intervention you could see the following notices in your log file after upgrading to PHP 8.1:
Passing null to parameter #1 ($string) of type string is deprecated
Passing null to parameter #1 ($num) of type int|float is deprecated
As well as the implementation of this 2nd RFC being inconsistent with what was promised in the 1st RFC, it also introduced a new inconsistency. Their pathetic excuse for doing it this way was given as:
For the new scalar type declarations introduced in PHP 7.0 an explicit choice was made to not accept null values to non-nullable arguments, but changing the existing behavior of internal functions would have been too disruptive at the time.
The words changing the existing behavior of internal functions would have been too disruptive at the time
shows that they clearly did not understand that the existing behaviour WAS to accept null values for all arguments (even the manual said so) and all that was necessary was to update the documentation for all function signatures. The comment about this being "too disruptive" shows that they were just too lazy to make the right decision and too stupid to realise that it was the wrong decision. Instead of taking on the work themselves and maintaining backwards compatibility they chose to force every userland developer to change their code to solve a problem which did not previously exist. The fact that they also missed what RFC: Strict and weak parameter type checking said about efficient programming shows that they are also incompetent.
In order to be classed a Type Safe a language must know type of data that each variable holds so that it can avoid crashes and data corruptions. There are basically two ways to achieve this depending on whether the language is compiled or interpreted as this determines whether the type checking can be performed at compile time or at run time.
Compiled |
This presupposes that all input from external sources - such as the user interface or the file/database system - is supplied in pre-defined data structures which define the type and size of each individual field/column within that structure. If a type mismatch is detected the compiler should generate an error message and abort the compilation process. If a program cannot compile then it cannot be run. As no type checking can be performed at run time the language cannot examine the contents of a variable to determine its type. It can only use the type that was specified where the variable was defined. This can cause a problem if the structure being received is not aligned with the structure that was sent as a variable may then contain characters which are incompatible with its type. This produces corrupt data which may not be detected for some time. |
Interpreted |
These requirements are based on the fact that all data structures supplied from external sources, such as HTML and SQL, contain nothing but strings. Instead of being supplied in static structures they are provided in dynamic arrays whose structures do not have to be defined before they are filled with data. If a type mismatch is detected at run time there are two ways in which this can be fixed:
|
The main reason why I do not have any type errors in my code is because I never split the array of application data into its component parts and load each part into its own property within each Model class. I leave the data in the $fieldarray variable which is used as both the input argument and output argument in every method call, which also includes all of the optional "hook" methods. The validation object which is built into my framework gets rid of any potential type errors by checking that every value can be coerced into its expected type.
Personally I regard strict typing as unfriendly because it is too strict. At the first hint of a type mismatch it will throw a TypeError. This stops the user's activity in its tracks as the program he was running has just aborted. It is also inconvenient for the developer as he has to hunt down the source of this "bug", modify the code to fix it, then recompile the program before it can be handed back to the user. In a dynamically typed language like PHP this situation is not a "problem", it is an "opportunity". It is an opportunity for the language developers to do something really intelligent and circumnavigate this problem with a simple yet elegant solution which is more convenient for both the user and the developer by automatically coercing the value from the "wrong" type into the "right" type. The program keeps running, there is no TypeError, and, more importantly, there is no data corruption.
This surely means that static typing is less intelligent and less user- and developer-friendly than dynamic typing. By association it could also mean that the lovers of static typing are less intelligent than their dynamically typed brethren.
Here endeth the lesson. Don't applaud, just throw money.
Here are some other articles which I have written on the subject of strict typing:
Here are some articles on strong and weak typing:
Here are some articles by people who actually like dynamic typing:
17 May 2025 | Added The problem with structures
Added The problem with arrays Added PHP is actually Type Safe Added How to avoid Type Errors Added Summary |
21 Apr 2025 | Added Static structures vs dynamic arrays |