PHP Classes - A Dissection

I have a love/hate relationship with PHP. My reasons for both can mostly be summarized in saying that it has evolved and adapted according to other programming languages. Having been around since 1994, PHP now has an estimated 4,887 functions (as of the very outdated version of 5.1.4) and is fortunately rather well documented. OOP is the way to  today, and unfortunately PHP seems to be a little bit late to the party.

According to get_defined_classes(), PHP 5.5.9 has 135 built-in classes. That's quite a few, but most of those you'll probably never use, and there is a very high probability that the class you wish existed is something you'll have to write yourself. Without comparing to any other language, I think that the simplicity as well as complexity of creating custom classes is a highlight of PHP.

The following will be a brief introduction into PHP classes, rapidly moving into more advanced topics. This is not intended to be a walk-through in any way, but an introduction to the concepts and a starting point for further research where needed.

The Basic Class

The most basic of classes in PHP is a collection of functions (now called methods) and possibly some variables (called properties) which are easily accessible throughout the class without needing to be passed along.

Throughout the class, $this is a special variable which refers to the current instance of the class itself. To make use of a class property $var, you would use $this->var. Similarly, to use a class method method, you would use $this->method().

<?php
/**
* Some documentation here...
*/

class MyClass
{
public $var = 'value';

function setVar($args)
{
$this->var = $args;
// Do something
return $this;
}
}

All class variables require declaring visibility, which may be public, protected, or private. Class methods are similar, though declaring visibility is optional, defaulting to public.

The visibility of a property or method can be defined by prefixing the declaration with the keywords public, protected or private. Class members declared public can be accessed everywhere. Members declared protected can be accessed only within the class itself and by inherited and parent classes. Members declared as private may only be accessed by the class that defines the member.

PHP: Visibility - Manual

All public methods and properties of a class can be used externally very similarly to how you would use them inside of the class, except replacing $this with a variable set when constructing the class.

<?php
// Create a new instance of MyClass and save as variable
$class = new MyClass();
// Call the setVar method of MyClass
$class->setVar('value');

// Retrieve and echo the value of $var in MyClass
echo $class->var;

You'll notice that I used $this as a return value for the function. When no return value makes sense or is necessary, returning the class object itself allows for methods to be chained together, which might make them easier to work with. Be warned, however, that it will also make your code more difficult to debug unless you're very careful.

$class->setVar('my value')->someMethod();

The :: or scope resolution operator

PHP classes can be used in many different contexts, much of which will be discussed later in sections dedicated to those specific topics, but it would be confusing to go further without introducing you to the :: operator as well as some important keywords. Unfortunately, I also cannot introduce the :: operator without mentioning things such as inheritance, which I will not talk about until later.

self and parent are keywords for class properties, methods, and constants which can be used to resolve overwriting when extending classes. These may only be used inside of classes, can be used for properties, methods, or constants, and use :: instead of -> as a class object would. For now just understand that it is an operator used inside of classes to specify which generation of a class property, method, or constant to use, and that the keywords function very similarly to $this except with a different operator.

static is another such keyword which uses this operator, and is my very next topic.

Static properties, methods, and usage

In addition to regular properties and methods, PHP also allows for static ones. Static properties and methods are accessible without creating an instance of a class and are maintained once an instance is created.

<?php
class StaticExample
{
public static $var = 'some text';

public static function greet($name)
{
echo 'Hello ' . $name;
}

public function getVar()
{
return static::$var;
}
}

// Elsewhere...

// Get the current value of the static property
echo StaticExample::$var; // "some text"

// Change the value of the static property
StaticExample::$var = 'Hello World';

// Call the static greet method
StaticExample::greet('John');

// Only now do we create a class instance
$class = new StaticExample();

// Despite having been set before the class instance
// was created, the static $var property is still
// what we changed it to

echo $class->getVar(); // "Hello World."

Namespaces

Aside from special circumstances, all classes should make use of a non-standard namespace. This does mean a little bit of extra code when writing classes as well as when using them, but it allows for much better organization and prevents conflicts between both PHP's core classes as well as any other classes which might exist elsewhere.

You can understand both the purpose as well as usage of namespaces by thinking of them as directories on a filesystem. You wouldn't want all of your files on the root directory of a drive, would you? That would make things very difficult to organize or sort though, and would prevent you from having two files of the same name.

Namespaces solve this problem for classes in PHP and many other programming languages. By default (meaning when nothing else is used), all classes in PHP use the root namespace of \. This applies to defined constants and functions as well, but I'm focusing on classes here. Also worth mentioning here is that, much paths on a filesystem, namespaces may be either absolute or relative depending on whether or not they begin with a \. As of PHP 5.5.9 at least, they may not use the .. operator, which is probably for the best.

<?php
// All namespaces much be declared without the leading \
namespace MyNamespace;

class MyClass
{
// Class properties, methods, etc.
}

// In another PHP script
// Assuming the default/root namespace,
// the leading \ is optional
$class = new \MyNamespace\MyClass();
$root_class = new \MyClass();
$other_class = new \OtherNamespace\MyClass();

Constants

PHP classes may also define constants. Like regular constants, any class constant is read-only and cannot be changed. They are, however, defined in a very different manner in that they are set using the const keyword rather than the define() function and that they are set at load time (before class is even constructed), so they may not be set within a method.

<?php
namespace MyNamespace\Constants;
class Example
{
const FOO = 'bar';

public function hasConst($name)
{
return defined('self::' . $name);
}

public function getFoo()
{
return self::FOO;
}

pubic function altGetFoo()
{
return $this::FOO;
}
}

// External use... Another script
$class = new \MyNamespace\Constants\Example;

// Could be used before class is constructed
// Done in this order to show relation to class
// use by name, self keyword, and object ($this)

echo \MyNamespace\Constants\Example::Foo; // "bar"
echo $class::FOO; // "bar"
echo $class->getFoo(); // "bar"
echo $class->altGetFoo(); // "bar"

Magic Methods

If you've created your own class in PHP before, whether you knew it or not, you've probably used at least one Magic Method already — __construct. Magic Methods are special methods in PHP (which begin with two underscores) and are called differently from other methods. I'll be sticking to the Magic Methods which I most frequently use and covering them fairly quickly by writing a class making use of these methods and describing their usage only in the documentation.

The following is a non-function sample class. Pay special attention to
@param, @return, & @example in method documentation rather than the code of the methods themselves.
<?php
namespace Magic;
class Methods
{
/**
* Private array for use with Magic Methods
* @var array
*/

private $data = array();

/**
* Called when using new \Magic\Methods()
*
* @param mixed ... Whatever given to constructor
* @return void
* @example $magic = new \Magic\Methods($arg1, $arg2, ...);
*/

public function __construct()
{
// Whatever done to create class instance
}

/**
* Called whenever class is destroyed, either at
* end of script or when unset() is used
*
* @param void
* @return void
* @example unset($magic);
* @example exit; // Anywhere PHP exits at
*/

public function __destruct()
{
// Do things you want done when class is destroyed
}

/**
* Called whenever setting undefined class property
*
* @param string $name Name of property to set
* @param mixed $value Value to set it to
* @return null
* @example $magic->$name = $value;
*/

public function __set($name, $value)
{
$this->data[$name] = $value;
}

/**
* Called when retrieving inaccessible class property
*
* @param string $name Name of property to get
* @return mixed Whatever its value is
* @example $some_var = $magic->$name;
*/

public function __get($name)
{
return $this->data[$name];
}

/**
* Called whenever checking if inaccessible var is set
*
* @param string $name Name of property to test
* @return bool Whether or not it is set
* @example if (isset($magic->$name)) { ...
*/

public function __isset($name)
{
return array_key_exists($name, $this->data);
}

/**
* Called whenever removing inaccessible property
*
* @param string $name Name of property to unset
* @return void
* @example unset($magic->$name);
*/

public function __unset($name)
{
unset($this->data[$name]);
}

/**
* Called whenever using inaccessible method
*
* @param string $name Name of method used
* @param array $args Array of arguments given
* @return mixed Whatever you want to return
* @example $magic->$method($arg1, $arg2);
*/

public function __call($method, array $args = array())
{
// Do something with $method & $args array
}

/**
* Method called when class used as a function
*
* @param mixed ... Anything given to the function
* @return mixed Whatever you want to return
* @example $magic($arg, $arg2, $arg3)
*/

public function __invoke()
{
// Do stuff here
}

/**
* Called whenever class used as a string
*
* @param void
* @return string
* @example echo $magic;
* @example $str = $magic . 'some text';
* @example exit($magic);
*/

public function __toString()
{
// Build up a string to return
}

/**
* Static version of __call
*
* @param string $method Name of method called
* @param array $args Array of arguments given
* @return mixed Whatever you want to return
* @example \Magic\Methods::$method($arg1, $arg2);
*/

public static function __callStatic(
$method,
array $args = array()
)
{
// Yada yada
}
}

Inheritance and Extending Classes

Once you've created a couple of classes, you might realize that you're creating slight variations of the same class and wishing there were a way to avoid creating all of that duplicate code. One of the great features of OOP is the ability to extend classes, and PHP is no exception.

Classes and methods may optionally use the abstract or final keywords to either require or restrict extending, respectively.

When you extend a class, you create a new class with all of the properties, methods, and constants of its parent (unless overwritten).

To use the typical example of class inheritance…

<?php
namespace Example;
abstract class Person
{
protected $gender = 'unknown';
protected $pronoun = 'He/she';
protected $age = 0;
protected $name = null;

final public function __construct($name, $age = 0)
{
$this->name = $name;
$this->age = $age;
}

final public function __toString()
{
$msg = "{$this->name} is {$this->gender}.";
$msg .= " {$this->pronoun} is {$this->age} years old.";
return $msg;
}

// Abstract methods have no body
abstract protected function requiredMethod($arg);
}
<?php
namespace Example;
final class Female extends Person
{
protected $gender = 'female';
protected $pronoun = 'She';

protected function requiredMethod($arg)
{
// Do stuff
}
}
<?php
namespace Example;
final class Male extends Person
{
protected $gender = 'male';
protected $pronoun = 'He';

protected function requiredMethod($arg)
{
// Do stuff
}
}
<?php
$jill = new \Example\Female('Jill', 42);
$bob = new \Example\Male('Bob', 57);
echo $jill;
// "Jill is a female. She is 42 years old."
As promised above, the difference between self, parent, and $this is when generation of the class you want to deal with.
  • self always refers to the class as it exists in the file it is used in.
  • parent always refers to the class which is being extended
  • $this refers to the class which was constructed
<?php
namespace Example;
class A
{
const NAME = 'A';
public function testSelf()
{
return self::NAME;
}

public function testThis()
{
return $this::NAME;
}
}

// Extending class... Another file
<?php
namespace Example;
class B extend A
{
const NAME = 'B';

public function testParent()
{
return parent::NAME;
}
}
// Yet another file...
<?php
$a = new \Example\A;
$b = new \Example\B;
$a->testSelf(); // 'A'
$a->testThis(); // 'A'
$b->testSelf(); // 'A'
$b->testThis(); // 'B'
$b->testParent(); // 'A'

Traits and Interfaces

Especially in more complicated examples of class inheritance, it may be helpful to make use of traits and interfaces. Though their purposes are very different from one another, they both serve the purpose of providing consistency when dealing with numerous classes.

You may also alias namespaces, classes, etc. using

use \something as alias

Traits

Traits in PHP are class-like in that they can have properties and methods, but they are only building-blocks of classes. They are useful for sharing methods between any number of classes without requiring extending classes. This is particularly useful if you want to inherit methods from multiple sources since a class may not extend multiple parents.

Tip: Traits may also use other traits, so you can create one trait to import several.

<?php
namespace Traits;
trait MyTrait
{
protected $foo = 'bar';

final public function getFoo()
{
return $this->foo;
}
}

// Elsewhere in another class
<?php
namespace Example;
class MyClass
{
use \Traits\MyTrait;
}

Interfaces

Interfaces are like a promise that a class implementing them will have a particular set of methods. The interfaces themselves do not provide these methods, but only serve to ensure that a class implementing them will provide a required set of methods and take the same arguments. This can be useful when refactoring classes and trying to be certain that certain critical methods remain compatible or in typehinting arguments to a function or method.

<?php
namespace Interfaces;
interface MyInterface
{
public function setVars(array $vars = array());
}

// Elsewhere...
<?php
namespace Example;
class MyClass implements \Interfaces\MyInterface
{
// Create the method for the interface here
public function setVars(array $vars = array())
{
// Method body... Do stuff
}

public function hinted(\Interfaces\SomeInterface $class)
{
// $class must implement \Interfaces\SomeInterface
}
}

Putting it all Together

On their own, any of the above can greatly simplify working in PHP. When properly used together, however, they can make creating new classes an almost magical process. You can complete extremely complex tasks by creating or using a library of traits for common methods, implementing interfaces to ensure compatibility, and extending your existing classes for specialized cases.

The following could be a fully-featured class of nearly any complexity, automatically adapting to changes and refactors, and with almost no code of its own… Just assembling building-blocks created elsewhere.

<?php
/**
* Class illustrating advanced PHP OOP techniques
*/

namespace Awesomeness;

use \Library\Namespace as Lib;

final class MyClass extends Lib\Abstracts\Class
implements Lib\Interfaces\Interface
{
use Lib\Traits\TraitA;
use Lib\Traits\TraitB;
use Lib\Traits\Magic_Methods;

const MAGIC_PROPERTY = 'data';

protected $data = array();

public function setLogger(Lib\Interfaces\Logger $logger)
{
$this->logger = $logger;
}
}

Start using classes, traits, and interfaces sooner rather than later. If structured and used properly, they can save you from a nightmare of technical debt down the road. Create a library of traits and abstract classes for commonly used functionality to make creating classes more consistent. Use interfaces to ensure compatibility.