Intro to Object Oriented Programming in PHP

OOP Basics

Object oriented programming (OOP) is a method of programming that is built around the concept of objects (go figure!). Inversely, procedural programming is based on actions or steps that occur sequentially.

Objects, much like the real world, can be thought of as a construct or entity with properties and actions.  Think about a ball.  A ball is an object.  It can have many properties (i.e. shape, color, size, etc).  A ball has actions (i.e. bounce, throw, catch, etc).  Let’s make a basic Ball class.  When I think of a ball, the first picture that pops into my head is a small, round, red object.  Let’s build that…

class Ball {
    public $shape = 'round';
    public $size = 'small';
    public $color = 'red';
}

By itself, this class does nothing.  It’s just a template, or classification (hey, that’s just a long word for “class”) of an object.  We see this round, small, red object and we classify it as a ball.  Here we have defined 3 properties for our Ball class.  For now, ignore the word “public” in front of each property.  We’ll get to that later…

 

Building an object from our class

If we want to create a Ball object from this class, we simply instantiate (build) it like this:

$myBall = new Ball();

When we create an object from this class, it will have the properties defined here (round, small, red) unless we override them.  The way to build an object from a class is to use a constructor method.  In OOP, method is basically a term that refers to a function built into a class.

Lets create a constructor method that will help build our Ball object.  The constructor method is a function built into a class that will help define our object.  We know balls come in many shapes, sizes, and colors.  Let’s use those as constructor variables (options) to help build our object instead of pre-defining them as class properties:

class Ball {
    public $shape;
    public $size;
    public $color;

    // Construct our ball object using these options
    public function __construct($shape = 'round', $size = 'small', $color = 'red') {
        $this->shape = $shape;
        $this->size = $size;
        $this->color = $color;
    }
}

Since we placed default values in the definition of the constructor method, we can still make a round, small, red ball with the same code:

$myBall = new Ball();
echo '<pre>'.print_r($myBall, true).'</pre>';

Output:

Ball Object
(
    [shape] => round
    [size] => small
    [color] => red
)

However, we now have the option to make an oval, large, blue ball by doing:

$myBall = new Ball('oval', 'large', 'blue');
echo '<pre>'.print_r($myBall, true).'</pre>';

Output:

Ball Object
(
    [shape] => oval
    [size] => large
    [color] => blue
)

 

Take our class further with methods

Still, a class that just defines properties has little advantage over using an ordinary PHP array.  Let’s put some motion into our ball:

class Ball {
    public $shape;
    public $size;
    public $color;

    // Construct our ball object using these options
    public function __construct($shape = 'round', $size = 'small', $color = 'red') {
        $this->shape = $shape;
        $this->size = $size;
        $this->color = $color;
    }

    // Change the color of the ball
    public function paint($color) {
        $this->color = $color;
    }

    // Make the ball large
    public function grow() {
        $this->size = 'large';
    }

    // Make the ball small
    public function shrink() {
        $this->size = 'small';
    }

    // Warp the ball into a different shape
    public function warp($shape) {
        $this->shape = $shape;
    }
}

Lets start with the default round, small, red ball and use some of the object methods to mutate our object:

$myBall = new Ball();
$myBall->paint('brown');
$myBall->grow();
$myBall->warp('oval');
echo '<pre>'.print_r($myBall, true).'</pre>';

Output:

Ball Object
(
    [shape] => oval
    [size] => large
    [color] => brown
)

Hey, look at that, we’ve turned our small, round, red ball into an official NFL football!

 

Property and method visibility

As previously mentioned the keyword public is used in our property and method definitions.  When a property or method is public, it can be accessed from outside the object.  Making properties public also allows them to be mutated from outside of the object.  So instead of writing this:

$myBall = new Ball();
$myBall->paint('brown');

We can accomplish the same property change link this:

$myBall = new Ball();
$myBall->color = 'brown';

While public properties may make your objects easy to modify, it’s not always the best approach.  Generally speaking, class properties and methods should only be given as much visibility as needed.

 

Don’t make everything public

Keeping our properties public can be convenient in some cases.  But it also makes it difficult to enforce any sort of rules or structure on your objects. We’ll first run through a scenario using the current public properties/methods, then we’ll see how we can improve by changing visibility.

Let’s say we want to make sure our ball object can only be 1 of 2 sizes, “small”, or “large”.  We want any other value to be considered invalid.  Our current implementation can’t enforce that.  We can easily set an undesired size on our ball object like this:

$myBall = new Ball();
$myBall->size = 'HUGE';

In addition, there is nothing in our class that is preventing the size from being set to an incorrect value type.  The following values don’t make sense for a “size” property, but will still technically work:

$myBall->size = true;
$myBall->size = -284.5;
$myBall->size = ['some', 'random', 'array'];

Using our current ball class, the only way to enforce a valid size would be outside the class. We’d need to do something like this (remember we only want “small” or “large” for sizes):

$myBall = new Ball();

if($newBallSize === 'small' || $newBallSize === 'large') {
    // Ball size is valid
    $myBall->size = $newBallSize;
}
else {
    // Handle invalid ball size
    die('Invalid ball size!);
}

This code does enforce a valid ball size for this one instance.  However, if we want to change the ball size again later on, we’d need to run the same conditional check before each mutation.  A much better way, is to take this same logic and put it into a class method:

// Set the size of the ball.  Must be either "small" or "large".
public function setSize($newBallSize) {
    if($newBallSize === 'small' || $newBallSize === 'large') {
        // Ball size is valid
        $this->size = $newBallSize;
    }
    else {
        // Handle invalid ball size
        die('Invalid ball size!');
    }
}

Now we can use our new setSize method to apply a value form outside our object like this:

$myBall = new Ball();
$myBall->setSize('large');   // Valid size, no error
$myBall->setSize(-284.5);    // Invalid, will show error message

So the new setSize method is an improvement over the original implementation.  However, we still haven’t prevented our size property from being mutated outside of the object.  The size property still has a public scope, which means we can still do this:

$myBall = new Ball();
$myBall->setSize('large');   // Valid size, no error
$myBall->size = -284.5;      // Invalid size, but still works

 

Protected and private visibility

Let’s change the size property to have protected visibility instead of public:

class Ball {
    public $shape;
    protected $size;
    public $color;

    //...
}

Having protected or private visibility means that the size cannot be mutated outside of the object.  The private scope is similar to protected, but it has further restrictions when dealing with inheritance.  We’ll tackle that topic in a separate post.  So now, the following will no longer work:

$myBall->size = 'large';

This will throw off an error:

Cannot access protected property Ball::$size

This is a good thing.  Now we can control the structure of our object in a much cleaner way.  By setting proper visibility we can encapsulate all of our logic and rules into the class itself.  We no longer need to worry about invalid size values being set from outside of the object.

 

Wrap it up

Object oriented programming is large topic.  It’s much too large to cover in a single write-up.  We’ve touched on some of the basics in this post.  We’ll continue to dive into more aspects in the future.

Here is our improved ball class in it’s final form.

class Ball {
    protected $shape;
    protected $size;
    protected $color;

    // Construct our ball object using these options
    public function __construct($shape = 'round', $size = 'small', $color = 'red') {
        // Handle invalid ball size
        if(!$this->isValidSize($size)) die('Invalid ball size!');

        $this->shape = $shape;
        $this->size = $size;
        $this->color = $color;
    }

    // Change the color of the ball
    public function paint($color) {
        $this->color = $color;
    }

    // Make the ball large
    protected function grow() {
        $this->size = 'large';
    }

    // Make the ball small
    protected function shrink() {
        $this->size = 'small';
    }

    // Set the size of the ball.  Must be either "small" or "large".
    public function setSize($size) {
        // Handle invalid ball size
        if(!$this->isValidSize($size)) die('Invalid ball size!');

        if($size === 'small') $this->shrink();
        else if($size === 'large') $this->grow();
    }

    // Check for a valid size value.  Must be either "small" or "large"
    protected function isValidSize($size) {
        return $size === 'small' || $size === 'large';
    }

    // Warp the ball into a different shape
    public function warp($shape) {
        $this->shape = $shape;
    }
}

 

Leave a Reply

avatar
  Subscribe  
Notify of