The Difference Between self::, static::, and parent:: in PHP

The Difference Between self::, static::, and parent:: in PHP

ยท

8 min read

Introduction

When working with PHP code, you may often come across parent::, static::, and self::. But when you're first starting out as a developer, it can sometimes be confusing to know what they do and the differences between them.

I'll hold my hand up and admit that for quite a long time after I first started as a developer, I thought static:: and self:: were the exact same thing.

So in this article, we're going to cover what each of these can be used for and the differences between them all.

What is parent::?

Let's start off with talking about parent::.

To get an idea of what it does, we're probably best off with looking at some code examples first.

Let's imagine that we have a BaseTestCase class that has a setUp method:

class BaseTestCase
{
    public function setUp(): void
    {
        echo 'Run base test case set up here...';
    }
}

(new BaseTestCase())->setUp();

// Output is: "Run base test case set up here...';

As we can see, when we call the setUp method, it runs as expected and outputs the text.

Now, let's imagine that we want to create a new FeatureTest class that inherits the BaseTestCase class. If we wanted to run the setUp method of the FeatureTest class, we could do so like this:

class FeatureTest extends BaseTestCase
{
    //
}

(new FeatureTest())->setUp();

// Output is: "Run base test case set up here...";

As we can see, we haven't defined the setUp method in our FeatureTest, so the one defined in the BaseTestCase will be run instead.

Now, let's say that we want to run some extra logic when we run the setUp method in our FeatureTest. For instance, if these classes were test cases that were being used as part of a PhpUnit test, we may want to do things such as creating models in the database, or setting test values.

At first, you might (incorrectly) think that you can just define the setUp method in your FeatureTest method and call $this->setUp(). I'll be honest, I always used to fall into this trap when I was first learning about programming!

So our code may look something like this:

class FeatureTest extends BaseTestCase
{
    public function setUp(): void
    {
        $this->setUp();

        echo 'Run extra feature test set up here...';
    }
}

(new FeatureTest())->setUp();

But, you'll find that if we were to run this code, we'd end up in an loop that would cause your application to crash. This is because we're recursively asking setUp to call itself over and over. You'd likely get an output similar to this:

Fatal error: Out of memory (allocated 31457280 bytes) (tried to allocate 262144 bytes) in /in/1MXtt on line 15
mmap() failed: [12] Cannot allocate memory
mmap() failed: [12] Cannot allocate memory
Process exited with code 255.

So instead of using $this->setUp(), we need to tell PHP to use the setUp method in the BaseTestCase instead. To do this, we can replace $this->setUp() with parent::setUp() like so:

class FeatureTest extends BaseTestCase
{
    public function setUp(): void
    {
        parent::setUp();

        echo 'Run extra feature test set up here...';
    }
}

(new FeatureTest())->setUp();

// Output is: "Run base test case set up here... Run extra feature test set up here...";

Now, as you can see, when we run the setUp method in the FeatureTest class, we're first running the code in the BaseTestCase and then continuing with the rest of the code defined in our child class.

It's worth noting that you don't always need to place the parent:: call at the top of the method. In fact, you can place it in the method wherever you'd like that best fits the purpose of the code. For example, if you wanted to run your code in the FeatureTest class first then the BaseTestCase, you could move the parent::setUp() call to the bottom of the method like so:

class FeatureTest extends BaseTestCase
{
    public function setUp(): void
    {
        echo 'Run extra feature test set up here...';

        parent::setUp();
    }
}

(new FeatureTest())->setUp();

// Output is: "Run extra feature test set up here... Run base test case set up here...";

What is self::?

Now, let's take a look at self::.

Let's imagine that we have a Model class that has a static connection property and a makeConnection method. We'll also imagine that we have a User class that inherits the Model class and overrides the connection property.

The two classes may look something like so:

class Model
{
    public static string $connection = 'mysql';

    public function makeConnection(): void
    {
        echo 'Making connection to: '.self::$connection;
    }
}

class User extends Model
{
    public static string $connection = 'postgres';
}

Now let's run the makeConnection method on both classes and see what output we'd get:

(new Model())->makeConnection();

// Output is: "Making connection to mysql"

(new User())->makeConnection();

// Output is: "Making connection to mysql";

As we can see, both calls resulted in the Model class' connection property being used. This is because self uses the property that is defined on the class where the method exists. In both cases, the makeConnection method is on on the Model class because one doesn't exist on the User class.

To further show this, we'll add the makeConnection method to our User class like so:

class Model
{
    public static string $connection = 'mysql';

    public function makeConnection(): void
    {
        echo 'Making connection to: '.self::$connection;
    }
}

class User extends Model
{
    public static string $connection = 'postgres';

    public function makeConnection(): void
    {
        echo 'Making connection to: '.self::$connection;
    }
}

Now if we were to call both methods again, we'd get the following output:

(new Model())->makeConnection();

// Output is: "Making connection to mysql"

(new User())->makeConnection();

// Output is: "Making connection to postgres";

As you can see, the call to makeConnection would now use the connection field on the User class because that's what where the method exists.

What is static::?

Now that we have an idea of what self:: does, let's take a look at static::.

To better understand what it does, let's update our code example above to use static:: instead of self:: like so:

class Model
{
    public static $connection = 'mysql';

    public function makeConnection()
    {
        echo 'Making connection to: '.static::$connection;
    }
}

class User extends Model
{
    public static $connection = 'postgres';
}

If we were to run the makeConnection method on both classes, we'd get the following output:

(new Model())->makeConnection();

// Output is: "Making connection to mysql"

(new User())->makeConnection();

// Output is: "Making connection to postgres";

As we can see, this output is different to when we used self::$connection earlier. The call to the makeConnection method on the User class has used the connection property on the User class rather than the Model class (where the method actually belongs). This is due to a feature in PHP called "late static binding".

If you're interested in reading more about late static binding, you can check out the PHP documentation here.

According to the PHP docs:

"This feature was named "late static bindings" with an internal perspective in mind. "Late binding" comes from the fact that static:: will not be resolved using the class where the method is defined but it will rather be computed using runtime information. It was also called a "static binding" as it can be used for (but is not limited to) static method calls."

So in the case of our example, the connection property on the User class is used because we're calling the makeConnection method on that very same class.

However, it's worth noting that if the connection property didn't exist on the User class, it would fallback to using the one on the Model class instead.

When to use "self::" or "static::"?

Now that we have a general idea of the difference between self:: and static::, let's quickly cover how to decide which of these to use in your own code.

It all really comes down the use-case of the code that you're writing.

In general, I'd typically use static:: instead of self:: because I'd want my classes to be extendable and provide support for if they're inherited from.

For example, let's say that I want to write a class that I fully intend to be inherited from by a child class (such as the BaseTestCase class in our example above). Unless I really wanted to prevent the child class from overriding the property or method, I'd want to use static::.

It would mean that I could have confidence that if I override any of the static methods or fields that my child class would use my overrides. I can't tell you how many times I've run into bugs in my code when I've used self:: in a parent class and then couldn't figure out why my child class wasn't using my override!

On the flip side, some developers may argue that you should stick to using self:: because you shouldn't really be inheriting from classes. They might suggest that you should be following the principle of "composition over inheritance" instead. I won't delve too much into this topic because that's for another blog post in the future. But in broad, simple terms, this principle states that you should avoid adding functionality to your classes by putting all your logic in a parent class, and instead add the functionality by building your class up with lots of smaller classes.

This means if you followed this principle, you wouldn't need to use static:: because you wouldn't ever be extending your parent class. If you wanted to ensure that the class couldn't be extended, you could even take the code one step further and use the final keyword when defining a class. Using the final keyword prevents a class from being inherited from, so it could reduce any worry in your mind that the class might accidentally be extended and introduce any potential bugs.

In general, it's usually best for you to decide on a case-by-case basis at the time of writing your code whether you should use static:: or self::.

Conclusion

Hopefully, this post should have given you an insight into the difference between static::, self::, and parent::.

If you enjoyed reading this post, I'd love to hear about it. Likewise, if you have any feedback to improve the future ones, I'd also love to hear that too.

You might also be interested in checking out my 220+ page ebook "Battle Ready Laravel" which covers similar topics in more depth.

If you're interested in getting updated each time I publish a new post, feel free to sign up for my newsletter below.

Keep on building awesome stuff! ๐Ÿš€

Source:

ย