This is the fifth part of a series. The first four parts are here:
- Looking at PHP's OOP from a CFMLer's perspective: classes, objects, properties
- Looking at PHP's OOP from a CFMLer's perspective: inheritance, static & abstract, interfaces
- Looking at PHP's OOP from a CFMLer's perspective: traits
- Looking at PHP's OOP from a CFMLer's perspective: namespaces
In PHP parlance "Overloading" is basically a mix of what in CFML we'd refer to as synthesised and implicit accessors.
First up, here's a CFML recap.
Synthesised accessors
Synthesised accessors (often erroneously referred to as implicit accessors, which is a different thing entirely: see further down). This is a feature of CFML wherein one can explicitly call getter or setter methods on an object without the relevant accessor being defined. EG:// Number.cfc
component accessors=true {
property english;
property maori;
public function onMissingMethod(){
writeDump(var=arguments, label="#getFunctionCalledName()#() call caught by onMissingMethod()");
}
}
// testNumber.cfm number = new Number(); number.setEnglish("toru"); number.setMaori("three"); number.setJapanese("三"); writeDump(var={ english = number.getEnglish(), maori = number.getMaori() }, label="From getters");
Here I have a simple CFC with two properties specified:
english
and maori
, and the only method is onMissingMethod()
, which is only used to demonstrate what happens when we call an accessor on a non-existent property.The test code calls
setEnglish()
and setMaori()
, which are synthesised setters for the english
and maori
properties. It also calls setJapanese()
which is not a synthesised setter, as there is no such properly as japanese
in this example.The results are as follows:
onmissingmethod() call caught by onMissingMethod() | |||||||||||||||||||||
|
From getters | |||||||||||
|
Note that
setEnglish()
and setMaori()
do not trigger onMissingMethod()
despite not actually being defined in the component; yet they still work fine, and set the properties correctly. On the other hand setJapanese()
doesn't exist, and is simply caught by onMissingMethod()
.Implicit Accessors
CFML also has implicit accessors (this needs to be switched on in Application.cfc). Implicit accessors are invoked implicitly (ie: without explicitly using them) when a property of a CFC is accessed. Here's an example:// Application.cfc
component {
this.invokeImplicitAccessor = true;
}
// Person.cfc
component {
property firstName;
property lastName;
public string function getFirstName(){
writeOutput("#getFunctionCalledName()#() called<br>");
return firstName;
}
public void function setFirstName(firstName){
writeOutput("#getFunctionCalledName()#() called with #firstName#<br>");
variables.firstname = arguments.firstName;
}
public string function getLastName(){
writeOutput("#getFunctionCalledName()#() called<br>");
return lastName;
}
public void function setLastName(lastName){
writeOutput("#getFunctionCalledName()#() called with #lastName#<br>");
variables.lastName = arguments.lastName;
}
}
// testPerson.cfm
person = new Person();
person.firstName = "Donald";
person.middleName = "Adam";
person.lastName = "Cameron";
writeOutput("Full name: #person.firstName# #person.lastName#<br>");
writeDump(person);
This time the accessor methods are defined, but they are never called. Simply referencing the property implicitly calls either the setter or the getter for it, as appropriate. Output:
setfirstname() called with Donald
setlastname() called with Cameron
getfirstname() called
getlastname() called
Full name: Donald Cameron
Component (Person) Only the functions and data members that are accessible from your location are displayed | |||||||||||||||||
|
The thing to note here is that the setters and getters for
firstName
and lastName
are indeed being invoked, despite no explicit reference to them. Also note that setting middleName
does not result in any special action, it just sticks a variable into the this scope of the object.Combining synthesised and implicit accessors
And the two concepts can be combined. Here's an example where the accessors are synthesised, and they are called implicitly:// Day.cfc
component accessors=true {
property english;
property maori;
}
// testDay.cfm
day = new Day();
day.english = "Tuesday";
day.maori = "Rātū";
day.japanese= "火曜日";
writeDump(day);
I'll not pore over this, but it just demonstrates there's no accessor methods defined, plus we never call them. But - as if by magic - the behaviour is as if they were there and they were called:
Component (Day) Only the functions and data members that are accessible from your location are displayed | |||||||||||||||||||||||||||||
|
Note that Railo actually does create the methods for me, behind the scenes. Note also that the
japanese
value is simply treated as an assignment of a this-scoped variable in the day
object.So that's the CFML scene set. How does PHP deal with this?
Overloading
I have no idea why PHP decided to call this overloading. Even PHP itself realises the confusion it would cause by co-opting an established term and using it to mean something different. This is from the docs:Note:So they knew they were doing something stupid, and still did it anyhow. This is reminiscent of some decisons Adobe has made with CFML. Except generally Adobe seem to be completely oblivious to poor decisions they make, I dunno which is worse. But I digress.
PHP's interpretation of "overloading" is different than most object oriented languages. Overloading traditionally provides the ability to have multiple methods with the same name but different quantities and types of arguments.
<?php
// Person.class.php
class Person
{
private $firstName;
private $lastName;
private $sex;
public function __set($name, $value)
{
echo sprintf("%s() called with %s, %s<br>", __FUNCTION__, $name, $value);
$this->$name = $value;
}
public function __get($name)
{
echo sprintf("%s() called with %s<br>", __FUNCTION__, $name);
return $this->$name;
}
public function __isset($name)
{
echo sprintf("%s() called with %s<br>", __FUNCTION__, $name);
return isset($this->$name);
}
public function __unset($name)
{
echo sprintf("%s() called with %s<br>", __FUNCTION__, $name);
unset($this->$name);
}
public function setSex($sex)
{
$this->sex = $sex;
}
public function getSex()
{
$this->sex = $sex;
}
}
So PHP goes wild with its underscores again here. Neat :-/Overloading provides a number of methods
__set()
, __get()
, __isset()
, __unset()
which are implicitly invoked when a property is accessed. One cool thing here is that as well as the obvious getter/setter treatment, it also caters for when testing if the property is set, and also when the property is "unset" (see example below). It's ugly as hell, but it all works OK.We've also got another even more egregious underscore fiesta with the
__FUNCTION__
"magic constant". It's kinda (kinda) like getFunctionCalledName()
in CFML. I'll do another quick article about this shortly.One quick thing to note in this code is this:
$this->$name = $value;
Note that this is different from a normal literal property assignment, eg:
$this->firstName = $firstName;
In the former example, the name of the key being set is itself a variable, not a literal as per the latter example. So in the
__set()
(etc) methods, the $name
variable is the name of the property. Not a property called "name", if that makes sense. Same as doing this[name]
in CFML.Here's the test harness:
<?php
// test.php
require "Person.class.php";
$person = new Person();
$person->firstName = "Donald";
$firstName = $person->firstName;
echo "First name: $firstName<br>";
echo "<hr>";
if (!isset($person->lastName)){
$person->lastName = "Cameron";
}
$lastName = $person->lastName;
echo "Last name: $lastName<br>";
unset($person->lastName);
echo "<hr>";
$person->middleName = "Adam";
$middleName = $person->middleName;
echo "Middle name: $middleName<br>";
echo "<hr>";
$person->sex = "Male";
$sex = $person->sex;
echo "Sex: $sex<br>";
This is simple (and much the same as the CFML example above). Here I test setters, getters,
isset()
and unset()
. I also set/get a property which is not previously defined, for good measure. Result:
__set() called with firstName, Donald
__get() called with firstName
First name: Donald
__isset() called with lastName
__set() called with lastName, Cameron
__get() called with lastName
Last name: Cameron
__unset() called with lastName
__set() called with middleName, Adam
Middle name: Adam
__set() called with sex, Male
__get() called with sex
Sex: Male
Observations:
- The handling of
$firstName
and$lastName
is predictable. - There seems to be a slight glitch when using overloading with undeclared properties: the
__set()
is called, but the__get()
isn't? However the value is still fished out correctly. Weird. I have some googling to do there. - If a property has explicit accessors defined, they are not called. The docs made no claim to, but it'd be nice if they were. The implementation seems incomplete to me here.
I found the answer to the issue with
__get()
not being called on access to $middleName
. It had come up on Stack Overflow: "__get
is not called if __set
is not called, however code works?". It's not exactly the same, but this explains it:
In your second example, you assigned a value to a property in your object. Since you did not declare the property in your object, it gets created by default as public. Since its public, __get
is never called when accessing it.
So this is actually working the same as the middleName
& japanese
examples from the CFML demos. Cool. Thanks, Stack Overflow.I probably waffled on more about CFML in this than PHP, but it took me a while to refresh my memory on how CFML worked to contrast it with PHP, and to check my expectations.
OK, next I'm gonna have a look at this
__FUNCTION__
thing...--
Adam