So I continue my battle through PHP's OO implementation. See "Looking at PHP's OOP from a CFMLer's perspective: classes, objects, properties" for the first bit of this.
Static properties and methods
This is something I've wanted in CFML for ages. Well: static methods, anyhow. There are tickets for both ColdFusion (3756518) and Railo (RAILO-2941) for this. I'm not so interested in static properties as I think they're perhaps the wrong way to deal with most situations? Dunno, I'm sure someone will come up with a compelling case...Anyway, so they're doable in PHP:
<?php
// Person.class.php
class Person {
public $firstName;
public $lastName;
private $fullName;
public static $population = 0;
public function __construct($firstName, $lastName) {
$this->firstName = $firstName;
$this->lastName = $lastName;
$this->setFullName();
self::$population++;
}
private function setFullName(){
$this->fullName = $this->firstName . " " . $this->lastName;
}
public function getFullname(){
return $this->fullName;
}
public static function getPopulation() {
return self::$population;
}
}
<?php
// person.php
require_once("app.php");
$boy = new Person("Zachary", "Cameron Lynch");
echo "Name: " . $boy->getFullName() . "<br>";
echo "Population: " . Person::getPopulation();
echo "<hr>";
$dad = new Person("Adam", "Cameron");
echo "Name: " . $dad->getFullName() . "<br>";
echo "Population: " . Person::getPopulation();
echo "<hr>";
$grandDad = new Person("Donald", "Cameron");
echo "Name: " . $grandDad->getFullName() . "<br>";
echo "Population: " . Person::$population;
echo "<hr>";
The output for this is:
Name: Zachary Cameron Lynch
Population: 1
Name: Adam Cameron
Population: 2
Name: Donald Cameron
Population: 3
Here we've added a static property
population
, which tracks the current head count of Person
objects. This is definitely not how one would deal with this information in the real world, but it'll do as an example.We also have a static method
getPopulation()
which we can use to fish-out the current value. Note because it's a public
property, we can also access it directly.In case you're not so familiar with static properties & methods, the thing to note is that they apply to the class, not to the instances of the class. that's why they're referenced via Person, above, not via
$boy
/ $dad
/ $grandDad
. Related to this, note that within the class, one refers to the current object via $this
, but the class itself via self
. Do not ask me why $this
has a $
and self
doesn't. Nor why references to static properties / methods use the ::
operator, but it's the ->
operator with object properties & methods. It makes no sense to me. Indeed WhyTF they don't just use .
is beyond me. I guess because they chose that to be the string concatenation operator (which, itself, is pretty stupid).Inheritance
PHP's allows one to extend on class with another using the extends keyword. Here's a basic example:<?php
// Person.class.php
class Person {
protected $firstName;
protected $lastName;
public function __construct($firstName, $lastName) {
$this->firstName= $firstName;
$this->lastName = $lastName;
}
public function getFullname(){
return $this->firstName . " " . $this->lastName;
}
}
<?php
// Employee.class.php
class Employee extends Person {
protected $middleName;
protected $employeeId;
public function __construct($firstName, $middleName, $lastName, $employeeId) {
$this->middleName = $middleName;
$this->employeeId = $employeeId;
parent::__construct($firstName, $lastName);
}
public function getEmployeeId(){
return $this->employeeId;
}
public function getFullname(){
return $this->firstName . " " . $this->middleName . " " . $this->lastName;
}
}
<?php
// employee.php
require_once("app_autoload.php");
$person = new Person("Zachary", "Cameron Lynch");
echo "Name: " . $person->getFullName();
echo "<hr>";
$employee = new Employee("Donald", "Adam", "Cameron", 17);
echo "Name: " . $employee->getFullName();
echo "Employee ID: " . $employee->getEmployeeId();
echo "<hr>";
The output is predictable:
Zachary Cameron Lynch
Donald Adam Cameron
Curse my son for having a compound surname, but that's
firstName lastName
in the first one, and firstName middleName lastName
in the latter one.There's some hairy syntax in there around calling the parent-class's constructor, but other than that: no surprises.
However the "no surprised" quickly devolved into confusion. I decided to try to use polymorphism on the
fullName
property, by setting it once when the object was created; however having a different setFullName()
method in each class:<?php
// Person.class.php
class Person {
protected $firstName;
protected $lastName;
protected $fullName;
public function __construct($firstName, $lastName) {
$this->firstName= $firstName;
$this->lastName = $lastName;
$this->setFullName();
}
private function setFullName(){
echo "Used setFullName() from Person<br>";
$this->fullName = $this->firstName . " " . $this->lastName;
}
public function getFullname(){
return $this->fullName;
}
}
<?php
// Employee.class.php
class Employee extends Person {
protected $middleName;
protected $employeeId;
public function __construct($firstName, $middleName, $lastName, $employeeId) {
$this->middleName = $middleName;
$this->employeeId = $employeeId;
parent::__construct($firstName, $lastName);
}
private function setFullName(){
echo "Used setFullName() from Employee<br>";
$this->fullName = $this->firstName . " " . $this->middleName . " " . $this->lastName;
}
public function getEmployeeId(){
return $this->employeeId;
}
}
<?php
// employee.php
require_once("app_autoload.php");
$person = new Person("Zachary", "Cameron Lynch");
echo "Class: " . get_class($person) . "<br>";
echo "Name: " . $person->getFullName();
echo "<hr>";
$employee = new Employee("Donald", "Adam", "Cameron", 17);
echo "Class: " . get_class($employee) . "<br>";
echo "Name: " . $employee->getFullName() . "<br>";
echo "Employee ID: " . $employee->getEmployeeId();
echo "<hr>";
This isn't a fantastic example, but it demonstrates the problem I encountered. Here's the output:
Used setFullName() from Person
Class: Person
Name: Zachary Cameron Lynch
Used setFullName() from Person
Class: Employee
Name: Donald Cameron
Employee ID: 17
Note that PHP doesn't pay attention to the fact that my subclass has its own
setFullName()
method. Huh?It wasn't until I was writing this article (and I've hastily had to rework the last paragraph, now that I see that it's my fault!) that I spotted what's wrong. My familiarity with CFML's peculiarities is getting the better of me.
Note this (from Employee.class.php):
private function setFullName(){
echo "Used setFullName() from Employee<br>";
$this->fullName = $this->firstName . " " . $this->middleName . " " . $this->lastName;
}
What does
private
access mean in PHP? Only the current class can see it. So my parent class - Person - can't see Employee's setFullName()
, so just uses its own one. If I change both functions to correctly use protected
access, the code works fine:
Used setFullName() from Person
Class: Person
Name: Zachary Cameron Lynch
Used setFullName() from Employee
Class: Employee
Name: Donald Adam Cameron
Employee ID: 17
Abstraction
One thing that PHP implements which CFML doesn't is class and method abstraction.Here we have a class that represents the abstract concept of a shape. For the purposes of this exercise all a shape has is a number of dimensions (eg: a circle has two; a sphere has three):
<?php
// Shape.class.php
abstract class Shape {
protected $dimensions;
public function getDimensions(){
return $this->dimensions;
}
}
It makes no sense to create a Shape object, as it is just a concept, not an implementation. So if we try:
<?php
// shape.php
require_once("app_autoload.php");
$shape = new Shape();
We get an error:
Fatal error: Cannot instantiate abstract class Shape in D:\Websites\www.scribble.local\shared\scratch\php\www\experiment\oo\shape.php on line 6
Note that the
Shape
class - whilst itself being abstract - does actually implement a concrete method, getDimensions()
. IE: once we have a concrete implementation extending this Shape
class, then its objects will inherit that method and it can be used quite normally.
Continuing on...
<?php
// TwoDimensionalShape.class.php
abstract class TwoDimensionalShape extends Shape {
protected $dimensions = 2;
abstract public function getPerimeter();
abstract public function getArea();
}
Here we have another abstract class, which extends
Shape
, this time a TwoDimensionalShape
. This time it's got a coupla abstract methods too. It's reasonable to conclude that all two dimensional shapes will have a perimeter and an area, but one cannot give a default implementation as the formulas vary according to the type of shape the implementation is. So this class cannot be instantiated, and its methods also are abstract, so they cannot be run: they simply define that the methods should be implemented (kinda like an interface).
<?php
// Circle.class.php
class Circle extends TwoDimensionalShape {
protected $radius;
public function __construct($radius){
$this->radius = $radius;
}
public function getCircumference(){
return 2 * pi() * $this->radius;
}
public function getPerimeter(){
return $this->getCircumference();
}
public function getArea(){
return pi() * $this->radius * $this->radius;
}
}
Finally we have a concrete implementation of
TwoDimenstionalShape
: a Circle. We can instantiate one of these, and call its methods:
<?php
// circle.php
require_once("app_autoload.php");
$circle = new Circle(7);
echo "Dimensions: " . $circle->getDimensions() . "<br>";
echo "Perimeter (circumference): " . $circle->getPerimeter() . " (" . $circle->getCircumference() . ")<br>";
echo "Area: " . $circle->getArea() . "<br>";
And this outputs some results:
Dimensions: 2
Perimeter (circumference): 43.982297150257 (43.982297150257)
Area: 153.9380400259
Cool.
What if we implement a
Square
, but don't implement the getArea()
method?<?php
// Square.class.php
class Square extends TwoDimensionalShape {
protected $sideLength;
public function __construct($sideLength){
$this->sideLength = $sideLength;
}
public function getPerimeter(){
return $this->sideLength * 4;
}
}
<?php
// square.php
require_once("app_autoload.php");
$square = new Square(7);
echo "Dimensions: " . $square->getDimensions() . "<br>";
echo "Perimeter (circumference): " . $circle->getPerimeter() . " (" . $circle->getCircumference() . ")<br>";
echo "Area: " . $circle->getArea() . "<br>";
This just results in an error:
Fatal error: Class Square contains 1 abstract method and must therefore be declared abstract or implement the remaining methods (TwoDimensionalShape::getArea) in D:\Websites\www.scribble.local\shared\scratch\php\www\experiment\oo\classes\Square.class.php on line 3
So if a class extends another class with abstract methods, then it either needs to implement the methods, or it needs to be declared as abstract. The bottom line is that any class being instantiated needs to have all its methods accounted for in a concrete way.
Interfaces
PHP, like CFML, allows interfaces. They're about the same, too:<?php
// Serialiseable.interface.php
interface Serialisable {
public function serialise();
}
I've adjusted the Shape class to implement Serialisable:
<?php
// Shape.class.php
abstract class Shape implements Serialisable {
protected $dimensions;
public function getDimensions(){
return $this->dimensions;
}
}
Note that it doesn't actually implement the required method though. I implement it in
TwoDimensionalShape
:<?php
// TwoDimensionalShape.class.php
abstract class TwoDimensionalShape extends Shape {
protected $dimensions = 2;
abstract public function getPerimeter();
abstract public function getArea();
public function serialise(){
return json_encode([
"dimensions" => $this->getDimensions(),
"perimeter" => $this->getPerimeter(),
"area" => $this->getArea()
]);
}
}
And in action:
<?php
// interface.php
require_once("app_autoload.php");
$circle = new Circle(7);
echo $circle->serialise();
Result:
{"dimensions":2,"perimeter":43.982297150257,"area":153.9380400259}
One thing you might have noticed is the file name of the
Serialisable
interface: Serialisable.interface.php
. My mentioning of interface
in the file name is not a PHP thing, it's just a file-naming convention. This did mean I needed to change my autoloader file though:
<?php
// app_autoload.php
spl_autoload_register(null, false);
spl_autoload_extensions('.class.php, .interface.php');
spl_autoload_register(function($class){
$filePath = "./classes/" .$class . ".class.php";
if (is_file($filePath)){
return require $filePath;
}
require "./classes/" .$class . ".interface.php";
});
I now need to do a coupla extra things:
- look out for an additional file extension
- and infer the file name of the class or interface slightly differently, checking to see if the class file exists first, and if not assume it's an interface
I was quite surprised that
return require
worked, given require is not a function. But it seems to. "Work", I mean.Type-checking
As a final step in this article, i'll just demonstrate that the type inheritance of all this is what we'd expect:<?php
// testCheck.php
require_once("app_autoload.php");
$circle = new Circle(7);
echo sprintf("A circle is a Circle: %b<br>", $circle instanceof Circle);
echo sprintf("A circle is a TwoDimensionalShape: %b<br>", $circle instanceof TwoDimensionalShape);
echo sprintf("A circle is a Shape: %b<br>", $circle instanceof Shape);
echo sprintf("A circle is Serialisable: %b<br>", $circle instanceof Serialisable);
echo "<br>";
echo sprintf("A circle is a Square: %b<br>", $circle instanceof Square);
That's self-explanatory. And here's the output:
A circle is a Circle: 1
A circle is a TwoDimensionalShape: 1
A circle is a Shape: 1
A circle is Serialisable: 1
A circle is a Square: 0
Cool.
Update (25/09/2014):
It came up in conversation today... so, like, what's the real difference between using abstraction and using interfaces? They do much the same thing, kinda, after all. Good question, and one I didn't cover here.Using abstraction allows one to having a partially realised / implemented class, knowing full well the rest of the conceptual implementation, but not the details: the details are handled by situation-specific subclasses. A two-dimensional shape definitely has a number of sides, and the TwoDimensionalShape class can implement that method (returning a property which is class specific). And a TwoDimensionalShape can dictate that conceptually any 2D shape will have area and perimeter. But it's down to the subclass to implement the logic to calculate them (eg: pr2 for the area of a Circle; x2 for the area of a Square). It's kinda planned refactoring in a way, I guess.
Using interfaces allows a given class to fulfil the requirements of more than one type: so a Circle can be a Shape (via inheritance) and something that is Serialisable by implementation. In strongly typed languages if a method requires a Serialisable object, one just cannot pass a Shape to it. However a Shape can be made to be a Serialisable object by stating it implements that Serialisable interface, and indeed implements the methods which the interface dictates. Once that contract is fulfilled, the method requiring a Serialisable object doesn't care that the thing being passed in is actually a Shape; it will have all the methods necessary to be used as a Serialisable object too.
Make sense? Just two slightly different approaches to a somewhat similar notion.
There's more...
There's a bunch more stuff to go through, but this has taken about two weeks to write now (not constantly), and I've had a long day today and I want a beer. So I'm gonna proofread it and press send. (Part 3 is now up: "Looking at PHP's OOP from a CFMLer's perspective: traits").Righto.
--
Adam