Sitemap
Harry Marx

Harry Marx

30 years experience - full stack, lead/analyst/developer. ERP systems.

Extending PHP

12 min readApr 14, 2026

--

Introduction

Having worked in many languages, I am partial to enhancing PHP syntax a bit…
For this I hooked into the auto_prepend_file setting in the php.ini, and made some syntax “enhancements”. I’m calling it Moina — as in a small member of the water fleas species.

Encapsulation, inheritance and polymorphism was the main drive force for building OOP. Yet OOP is not the only paradigm with dominion over these concepts. I would like to explore how some of these technical concepts in OO, can be realized in non-OO domains. The reason being the overhead in many OO languages, and syntactical verbosity.

Encapsulation is the collecting of variables (properties) into a structure (a class). Polymorphism is mapping functions (methods) to these structures. And then inheritance is the mechanism to progress from abstract classes to specialized implementations thereof.

I will discuss PHP without referring to its OO implementation.
I have developed a parser to run on PHP files before the official engine takes over, and it brings a few ideas to the front.

Encapsulation

‘Moina’, the parser, promotes the easy grouping of fields into structures, with the minimum of syntax, using a variadic function as intermediate.
Consider the syntax for associative arrays:
$A = [‘name’ => ‘harry’, ‘job’ => ‘developer’ ];
and accessing it:
echo $A[‘name’] . ‘ is a ’. $A[‘job’];
Could this be less verbose?

PHP does allow the creation of an object, based on associative arrays:
$O = (object) [‘name’ => ‘harry’, ‘job’ => ‘developer’ ];
echo $O->name . ‘ is a ‘ . $O->job;
This is slightly less syntactical.

Such an object instantiates stdClass, and as such does not have methods. For our purposes we can call this a Structure, borrowing from C/C++. But PHP also allows named parameters when calling functions, and variadic functions which accepting any number of arguments. When calling a variadic, with a named parameter, you can actually use any name for the argument. Combining these concepts into a type casting function, like in:
function obj( …$x ) { return (object) $x; }
simplifies the declaration too:
$O = obj( name: ‘harry’, job: ‘developer’ );
echo $O->name . ‘ is a ‘ . $O->job;

And then we can have the parser taking typical javascript syntax, and rewriting it in the above format, from:
$O = { name: ‘harry’, job: ‘developer’ };
echo $O.name . ‘ is a ’ . $O.job;
(The parser sees /(\$[\w._]+)\.(\w)/ as the object operator.)

Is this syntactical simplification clear? Does it contributes to readability,
and less cross language barriers?

Polymorphism

This allows multiple definitions using a single function name, to be mapped each to a particular structure. In OO this usually is done through abstract classes and (inheritance) extended implementations.
But another concept is fairly well defined — function overloading. NOT the PHP definition, but the “standard” definitions of “overloading”. This is where the mapping is done based on the type of the values the function is called with, and the types of values the function accepts. We will call this the arguments’ signature, and the function’s signature.
In OO the namespace problem is approached from the variable’s perspective:
$circle->draw();
where circle is some object, an instantiation of a Circle class, which is derived from perhaps an abstract Shape class.
Typically the Shape class presents an abstract draw() method.

In a non-OO domain, a draw() function would be defined, which accepts as argument, a particular shape:
draw( $circle );
(For me, this instruction flows more naturally with the verb before the noun.)
Moina allows for the redeclaration of the function draw() (using the ‘overload’ keyword), where each version would accept a different pattern of argument types — each has a different signature.
overload function draw( circle $S ) { }
overload function draw( square $S ) { }
…etc.
Note important differences here. First, the selection of the function to be executed, the overload resolution, happens NOT on the type of the variable, but on the type of the value in the variable. This takes advantage of the “untyped” nature of PHP. All values in PHP have a type, but the type is associated with the value, and not the variable (the container), in which the value is stored. And then the overload resolution happens at runtime, not compile time.

I could define structures now as follows:
$circle = { _type: ‘circle’, x:100, y:100, radius:30 };
Setting the property _type causes the function valtype() to return ‘circle’ when used to resolve overloading, and so we have typed structures.
I can also use the struct() function:
$circle = struct( circle: { x:100, y:100, radius:30 } );
(Much more elegant…)

And a function to get its area:
overload function area( circle $c ) { return $c->pi() * radius ** 2; }
or even using Moina’s double-fat-arrow declarations:
overload area( circle $c ) ==> pi() * $c.radius ** 2;
(note the dot operator doubling for the object operator)

More examples and other ideas

An example of a simplified for-loop syntax, for simple loops:
for $i=1..10 { echo $i; }

Declaring a structure
$s = { one:1, two:2 }; // curlies preceded with the characters ( , : = becomes objects
$s = struct( person: {name: ‘harry’ } ); // a typed structure

Simplified function definitions: double-fat-arrows:
add($a, $b) ==> $a + $b;
add2($a, $b) ==> { return $a + $b; }
// fat-arrow function with {body}
and as a callback:
preg_replace_callback( ‘[a-z]’, ($match) ==> strtoupper($match[0], $a );
(note here the superfluous and now missing ‘fn’)

(You can also use ==> for “functions” without parameters.
myName ==> ‘harry’;
Conceptually these becomes global constants, that accepts expressions…)

Another way of using declaratives is as both “getters” and setters”.
If you declared:
staff_name( $number, $name=null ) ==> { global( $STAFF);
if ($name) $STAFF[$number] = $name;
return $STAFF[$number] = $name; }
you can use it in an “assign” statement where the value takes the place of the last parameters:
staff_name( 123 ) = ‘harry’;

extend and override allows even more OO concepts to spill into non-OO PHP. Assume str() is an existing function. This is an example of a sequence of extends, and calling previously extended versions:
str( $x ) ==> print_r($x, true ); // the “root” form
extend str( $x1 ) ==> ‘v1:’ . str_($x1);
//note the trailing underscore to call
extend str( $x2 ) ==> ‘v2:’ . str_( $x2 );
//to chain to previous versions.
extend str( $x3 ) ==> ‘v3:’ . str_( $x3 );
echo str( 1 );
// output : v3 v2 v1 1

If you uncomment the next instruction, it nullifies the above extensions,
and the output of the above echo changes accordingly
//override str( $x ) ==> $x;
Overriding or extending a function does so globally !!!

However, to extend an overloaded function, simply overload the function again, with the same type signature. Moina will replace the previous overload with the new one.

More complex overloading:
Define a new function display(), that uses an overloaded function val() to format the output. The overload resolution uses the Moina function valtype(), which can be extended.

function display( …$x ) {
foreach ( $x as $i => $xx ) {
if ( $i ) echo ' ';
echo val( $xx );
}
echo '<br>';
}

val( $i ) ==> print_r( $i, true ); // base function, non-overloaded default
overload val( integer $i ) ==> 'ival=' . $i; // should be called for integers
overload val( string $s ) ==> 'sval=' . $s; // should be called for strings

Here we defined independent overloaded versions of val() that will be called based on the type returned from valtype(), for the actual value passed.
We can extend valtype() to return some custom type based on some arbitrary condition:
extend valtype( $v ) ==> $v === 1 ? ‘number’ : valtype_($v);
Calling:
display( valtype(1), valtype(2), valtype( ‘xxx’ ) );
returns: number integer string
And lastly, we can define an overload formatter now for exactly this new custom type:
overload val( number $i ) ==> ‘#’ . $i;
display( 1, 2, ‘x’ );
returns: #1 ival=2 sval=x

Note — valtype() will, if an object has a property ‘_type’, return its value as the object’s type.
This allows for “typed structures”:
$to = { _type: ‘developer’, name: ‘harry’ };
This is equivalent to:
$to = struct( developer: {name: ‘harry’} );
In fact, the function struct() just adds the _type property to the object.(Untyped structures will set _type to null, and valtype() will return stdClass for these.)

The dot-operator has now two functions — accessing an object’s property, or concatenating strings. If any of the two character immediately next to the dot is a not alphabetic, or the preceding symbol is not a variable, it is seen as concatenation.
If in the form $variable.symbol, it is used as an object operator.
disp( $to.name . ‘ is a ’ . valtype( $to ) )
returns: harry is a developer

Get Harry Marx’s stories in your inbox

Join Medium for free to get updates from this writer.

Lets try:
display( [1,2,3] );
If the below definition is not added, and we did not define a “base” function ( a non-overloaded function), you will get:
Exception: overload signature not found: val(list)
This indicates that display() could not find an appropriate val() with which to display the array — that takes a “list” as argument.
You can resolve this by either defining a root function (no overload), which will handle all un-catered types, or by defining an overload:
overload val( list $x ) ==> ‘list=[’ . join( ‘,’ , $x ) . ‘]’;

Even more complex signatures — multiple parameters

The pattern of arguments forms the “signature” of the overload to call.
If not found, overload resolution will drop the last type, and search again, until a root overload is reached. Of none is found, it will fail and throw an exception as per the above examples.
This shortening of the signature we will call degenerative overloading.

overload function do_x( integer $i ) { disp( "do1int: $i" ); }
overload function do_x( integer $i, integer $j ) { disp( "do2int: $i $j" ); }
overload function do_x( integer $i, integer $j, integer $k ) { disp( "do3int: $i $j $k" ); }
overload function do_x( string …$s ) { disp( "do strings:", …$s ); }
do_x(2); //do1int: 2
do_x(2,3); //do2int: 2 3
do_x(2,3,4); //do3int: 2 3 4
do_x(2,3,4,5); //do3int: 2 3 4 //degenerative match
do_x( 'a','b','c' ); //do strings: a b c //degenerative match

Some limitations of overloading:
- Only the last parameter of overload functions may be untyped
- You cannot use ‘mixed’ to match any type
- The argument signature can only be larger than the function signature, therefore default values and optional parameters are not applicable.

Degeneracy approaches inheritance.
If the arguments’ signature does not match, overloading tries a degenerative signature. But it also can use a signature expression.
Consider:

overload area( shape $s) ==> 'undefined';
overload area( circle $s) ==> 'circle area is:' . pi() * $s.radius ** 2;
overload area( rectangle $s) ==> 'rectangle area is:' . $s.width * $s.height;
overload area( square $s) ==> 'square area is:'. $s.length ** 2;
//making some simple overloaded calls:
disp( 'circle:', area( struct( circle: { radius:10 } ) ) );
disp( 'rectangle:', area( struct( rectangle:{ width:10, height:10 } ) ) );
disp( 'square:', area( struct( square: { length:10 } ) ) );

Now if we consider Moina defining the overload signature to delimit the types with an underscore, we could have tried this:
disp( area( struct( shape_square: { length:10 } ) ) );
It will attempt to use a ‘shape_square’ overload, and then degenerate to a ‘shape’ overload. Perhaps not the intended idea.

But there are also some useful operators, similar to union types on parameters. The following call will use square then shape, to find an overload, as alternatives:
disp( area( { _type: ‘square|shape’, length:10 } ) );
Here the _type syntax must be used, as square|shape is not a valid parameter name. ‘a|b_1|2’ will try a_1, a_2, b_1 and lastly b_2.
The following call will use a square overload, then shape, as if shape is a super type, and square a sub type:
disp( ‘shape then square:’, area( { _type: ‘shape>square’, length:10 } ) );

The following call will use a square overload, then shape, in sequence as listed:
disp( ‘square,shape:’, area( { _type: ‘square,shape’, length:10 } ) );

No — these are not all the same. For example the following type expression: string>staff_name|surname, client_name|surname,
will evaluate to overloads searched for, in this order:
[0] => staff_name
[1] => staff_surname
[2] => string
[3] => client_name
[4] => client_surname
These operators has hi to lo priority: _ | > ,

You can also use type expressions when overloading a function:
overload do_x( staff|client …$s ) ==> “do staff/client: “ . str_struct( …$s );
which will serve:
disp( do_x( struct( staff: {name:’harry’} ) ) ); //and also
disp( do_x( struct( client: {name:’harry’} ) ) );

This allows multiple inheritance, where an object has access to multiple “parent” versions, but defined per function, or per object itself.

For completeness sake, there are alternative mechanisms in native PHP, which allows polymorphism inside a function.
The first uses getType() on an untyped parameter, which will return the native type of the value passed:
function do_show( $x ) {
if ( getType($x) === ‘integer’ ) echo ‘integer:’ . $x . ‘<br>’;
if ( getType($x) === ‘string’ ) echo ‘string:’ . $x . ‘<br>’;
}
This approach limits the polymorphism to native PHP types, does not allow new types to be processed, and you need to modify the existing do_show() function. In contrast, OOX does allow new forms of the function, without
modifying existing code.

The second alternative is to use named parameters with a variadic function. The passed values are packaged in an associative array.
function do_show( …$x ) {
if ( isset( $x[‘int’] ) ) echo ‘int:’ . $x[‘int’] . ‘<br>’;
if ( isset( $x[‘str’] ) ) echo ‘str:’ . $x[‘str’] . ‘<br>’;
}
This approach is not limited to native or even define types, but totally dynamic. It does require the arguments be named correctly though.
It also does not isolate previously written code like an overload approach does.

Let us visit our shapes again. Consider:

//Here we have a base function and overloads for 3 shapes
overload area( shape $s ) ==> 'shape area undefined: ' . print_r($s, true);
overload area( circle $s ) ==> 'the area of this circle is ' . pi() * $s.radius ** 2;
overload area( square $s ) ==> 'the area of this square is ' . $s.side ** 2;
overload area( rectangle $s ) ==> 'the area of this rectangle is ' . $s.width * $s.height;

//now we repeat the overload for the circle
overload area( circle $s) ==> 'the area of this circle v2 is ...' ;
//this in effect replace the previous overload
//output: the area of this circle v2 is ...

But what would happen if we did this:
extend area( circle $s ) ==> ‘the area of this circle v3 is …’ ;
Nothing! Because to replace an overload, you must overload it again.
This extends a base function (!!!), and engage PHP’s type checking for a type called ‘circle’.
If we now call it with a non-overloaded type, it will attempt to call the base function, and will thow a type error: TypeError: area_6_(): Argument #1 ($s) must be of type circle, stdClass given.
Removing the circle type in the extend, opens up the base function to all types again.

Some syntax enhancement:

for $i=1..10 {…}
This will be replaced with : $_ii_ = 10; for( $i = 0; $i ≤ $_ii_; $i++) { … }
Making simple for-loops more elegant. You can even do this:
for $i = $i .. $i+1; which will execute exactly twice.

enum { a, b, c }
will define constants a, b and c, equal to 1,2 and 4 respectively.

==>
double-fat-arrow functions — for lambdas and all function, makes the “fn” and “function” redundant.

{}
To define structures, defined as stdObjects — “objects” without methods.
If it follows any of these chars : , = ( the curly brackets is replaced with a call to obj() which wraps the content as a stdObject.

case()
A syntactical simple switch():
case ( $x ) {
0: echo ‘zero’;
1,2,3: echo ‘1,2, or 3’;
else: echo ‘some other number’;
}
It does a break after the last statement of each condition set.

An lastly, I find a few functions useful :

substring( $string, $from $to ) 
substring_replace( $str, $ins, $from, $to )
// gets or display $from up to and including $to.
// $from and $to can be negative, or string delimiters
strposrev( $hay, $needle, $from ) // search towards $hay[0]
//ignoring all after and at $from
str_paired( $str, $from, $bra='(', $ket=')' ) //get paired closing bracket
array_map_keys( fn($k,$v), $A) // does a map, passing key and value
split( $d, $s, $n=0 ) // does explode, and if n>0 return exactly $n elements
splitt( $d, $s, $n=0 ) // same as split(), but trim and filter elements
hide($s) // set bit 8 on all chars in a string
unhide($s) // clear bit 8 on all chars in string
formstr( string $t, $p, $truc=true ) //does formatted interpolation
/* $t is the template, $p is an object or associative array
Expressions can be evaluated into $p.
$trunc controls if fields may be truncated if too long
"{name=harry}" template can provide a default
"{name} {name%9} {name%9.9} {name%x9.9}" where x can be: n/d/s/f
%n number %n99.9 width.decimals never truncated
%n, number with thousands separator, never truncated
%d date 'yyyy-mm-dd HH`hmi:ss dow Month Mon fff yy hh m h w' never trunc
%s string may be truncated
%f uses sprintf() to format
*/

In summary

Implementing new syntax, and even extending OO concepts to functions, is quite possible. Indeed, it can be syntactical much less complex. The same modularity achieved in OO, is then possible with functions, as one can extend or overload a function, without touching the previous code. This alleviate the need to test the existing code again.

Simpler syntax always wins. The general problem with web-applications, as is typically what PHP is used for, is not speed or memory limitations. The major issue is maintainability. Here reading the code, and understanding what the developer wanted to do, is crucial.

As such, I might just be using Moina for simpler syntax, and modularity in my next project…

You can access and use this pre-parser from here:
https://github.com/marxharryhm-hub/PHP-overloading

--

--

Harry Marx
Harry Marx