Brief Intro to OOP in PHP

So here we are finally ... object oriented programming. We're about to become pro's right?
Well - to be perfectly honest. This will mostly be an introduction to classes and objects in PHP. So I'm abusing the term here a bit - sorry.

TLDR: Objects are simply a way to encapsulate related data and/or behavior into a single entity, while classes define their structure and capabilities.

If I think back a few years, my first thoughts regarding oop were like

  • "OMG, I think I don't get anything here at all o.O"
  • "What the heck - I need to write more code to get the same things done!"
  • "I don't get the point ... this oop seems utterly useless!"
  • "Why does everything look so complicated now!"

If you are having the same thoughts during your first intimate moments with classes and objects - cool.
Once you've really grasped the concept, you will be like "wow - it's so easy!". But getting to that point might take some time (days, weeks ... even months) - and that's perfectly normal unless you're some kind of genius.

Defining our first Class

Since I've mostly been involved in e-commerce and ERP related projects for the past few years, we are starting off with products for an imaginary product management tool.

If you know one or two things about databases - the class is basically the schema. First, you define the schema, then you can fill it with data. It's a bit far fetched ... but not too far I guess.

To keep things simple, all our product needs is an SKU and a name. SKU is the abbreviation for stock keeping unit. It's basically just a unique code we use to identify our product. If we had green shoes, that SKU could be GS1. The name will be just that e.g. Nice Green Shoes.

First, we tell PHP that we want to define a class with the keyword class, followed by the name of the class - in our case Product. The actual body of the class has to be wrapped in curly braces {}.
Next, we define the properties $sku and $name. Properties are the variables of a class. Other than that, there's no real difference between a property and a variable.
The public keyword in front of the property names defines the visibility or rather accessibility of the properties.
But for now, you don't need to understand it in detail, just follow along and write the code. We'll figure out the rest along the way =)

class Product
{
    public $sku;
    public $name;
}

Instantiating our Class

Alright - now that we have our class defined, we can start creating our products. Just defining the class by itself doesnt do anything visible yet. To create an object from a class we use the new keyword - this is called instantiating - and assign it to the variable $product.
Let's check what we got using print_r();

class Product
{
    public $sku;
    public $name;
}

$product = new Product;
print_r($product);
Product Object
(
    [sku] =>
    [name] =>
)

Not too impressive yet huh ... so let's assign some property values by using the arrow operator -> to access $sku and $name. Just as with regular variables the assignment operator = is used to assign the values.
By the way - if you are using a proper code editor already, it will most likely suggest the property names once you type $product->.

class Product
{
    public $sku;
    public $name;
}

$product = new Product;
$product->sku = 'NGS001';
$product->name = 'Nice Green Shoes';
print_r($product);
Product Object
(
    [sku] => NGS001
    [name] => Nice Green Shoes
)

Alright - we're getting somewhere here. Now let's change something in our class, just to see what happens. Let's set the visibility of $sku to private and then run the code again.

private $sku; // change public to private
PHP Fatal error:  Uncaught Error: Cannot access private property Product::$sku

Oops xD - now we're getting a fatal error. That's because a private property is only accessible from within the class. This will not make any sense to you yet. Why would need a variable we cannot access?
Don't worry, you'll understand soon enough.

The Class Constructor

Constructor is the fancy name for a function, that is magically executed when you create an object using the new keyword.
By the way, functions that belong to a class are called methods. Just like before with variable and property, functions and methods are the same thing. The only difference is, that a method is a function which belongs to a class. Just like properties, methods have a visibility as well. For our constructor we're using public again.
The method name (function name) of the class constructor is predefined as __construct(), but what you do within it is completely up to you. Let's start with a simple example.

class Product
{
    public $sku;
    public $name;

    public function __construct()
    {
        echo "Hello! I'm new here!\n";
    }
}

$product = new Product;
echo "See - the constructor was executed first!\n";
Hello! I'm new here!
See - the constructor was executed first!

Now we know for sure, the constructor is executed right in the beginning. Time to do something more useful with it and change our class a little. By adding the $sku and $name parameters to the constructor, we're enforcing these two to be passed when creating a new instance of the class.
In the constructor method itself, we assign the values immediately to their respective properties.
Try running the code like it is below and watch it fail =)

class Product
{
    public $sku;
    public $name;

    public function __construct($sku, $name)
    {
        $this->sku = $sku;
        $this->name = $name;
    }
}

$product = new Product;
PHP Fatal error:  Uncaught ArgumentCountError: Too few arguments to function Product::__construct()
...

Just as with a regular function - if there are mandatory arguments with no default parameter values, we have to pass them to the constructor. Another thing to note about the constructor. It's pointless to add a return statement. There are only two options - either the instantiation fails or we get an instance of the class (an object).

Lets try that again then. We leave the class definition as is, but when we create the product, we pass the $sku and $name to the constructor like so:

$product = new Product('NGS001', 'Nice Green Shoes');
print_r($product);
Product Object
(
    [sku] => NGS001
    [name] => Nice Green Shoes
)

As you can see, we can create the object with proper values immediately. But then again ... proper values. Maybe we want to double check that first.

$product = new Product('', 123);
print_r($product);
Product Object
(
    [sku] => 
    [name] => 123
)

It seems we can pass whatever to the constructor right now. That's no good. Let's define some minimal rules and add logic for validation here.
For the SKU we always want it to have a length of 6 characters. As for the name, it should be at least 2 characters long.

Private Methods for our Class

Since we have not tried that yet, lets create two private methods (functions) within our product class and call them from the constructor. We make them private for now, because they will only be used inside our class.

For the constructor to be able to call a method of its own class, we use the keyword $this and the arrow operator ->, followed by the method name.

class Product
{
    public $sku;
    public $name;

    public function __construct($sku, $name)
    {
        $this->throwOnInvalidSku($sku);
        $this->throwOnInvalidName($name);
        $this->sku = $sku;
        $this->name = $name;
    }

    private function throwOnInvalidSku($sku)
    {
        if (!is_scalar($sku) || strlen($sku) !== 6) {
            throw new InvalidArgumentException('Invalid product sku'); 
        }
    }

    private function throwOnInvalidName($name)
    {
        if (!is_scalar($name) || strlen($name) < 2) {
            throw new InvalidArgumentException('Invalid product name');
        }
    }
}

// try playing a bit here by passing valid and invalid 
// values to the constructor and see what happens =)

try {
    $product = new Product(1 ,2);
    echo "Yay, we created a valid product!\n";
} catch (InvalidArgumentException $e) {
    echo $e->getMessage() . PHP_EOL;
}

print_r($product);

Since there's happening quite a bit more in our class now - lets take a look at what we got here.

In the constructor, we pass the $sku to the throwOnInvalidSku() method. Within the method, we first check whether the value in $sku fits our rules. If the value is ok, nothing happens, but if the value is invalid, we use the throw keyword to throw a so called exception.

Exceptions are special classes, that cause PHP to stop and emit an error, which in this case, cancels the instantiation of our class.
Now when we create our product object, we wrap into a so called try-catch-block try {} catch () {}.

But lets not go into too much detail about exceptions here. We need an extra chapter for that.

It's enough for you to understand, that you can ... todo: rework chapter + it's too early to introduce exceptions ...