PHP 7.4 : Typed Properties
16, December 2019

Scalar types & return types are one of the killer improvements of PHP 7 era. By defining what our functions/methods accept and return, we can write more reasonable code while avoiding edge-cases of unexpected data type.

In PHP 7.4, type system became more powerful with "Typed Properties". As almost all the modern PHP Frameworks now follow Object Oriented Paradigm, all of them now will be able to armor themselves as "strict manner".


What is Property Types and How to use them?

consider below class

<?php
declare(strict_types=1);

class Student {
    public $name;
    public $score;
}

Here we have a class "Student" With only two property. Currently none of them have any type specified. So we can create a student object like this

$student = new Student();
$student->name = 'John';
$student->score = 60;

var_dump($student);

// Output
object(Student)#1 (2) { ["name"]=> string(4) "John" ["score"]=> int(60) }

All fine. But, there is a gotcha here. As we have no type defined, we can set any type of value as student score. See

$student = new Student();
$student->name = false;
$student->score = 'invalid value';

var_dump($student);

// output
object(Student)#1 (2) { ["name"]=> bool(false) ["score"]=> string(13) "invalid value" }


But my business logic requires that student name must be string and score must be integer. How can we solve this? Lets use PHP 7.4 and implement typed properties in our class "Student"

<?php
declare(strict_types=1);

class Student {
    public string $name;
    public int $score;
}

here, we have defined $name as string and $score as int. Now, if we try to initiate with invalid value, PHP will throw an Exception

 $student = new Student();
$student->name = false;
$student->score = 'invalid value';

var_dump($student);

// output
Fatal error: Uncaught TypeError: Typed property Student::$name must be string, bool used


How many Types can I use?

Property type declarations support all type declarations supported by PHP, with the exception of void and callable. This means below types are usable

  • string
  • int
  • float
  • bool
  • array
  • iterable
  • object
  • self & parent
  • Classes & interfaces

Note: You can prefix "?" with type name to define the property as nullable.

public ?int $number;

$number property will accept both null and int as valid type.


Type Strictness

Another Great addition of PHP 7 is "Type enforcement". For a long time, PHP follows a procedure called "Duck Typing" Which lets you juggle type of a value as much as you want (Even when you don't want). Due to this behavior it was okay to pass string to a function that operates on integer (?!?)

Thanks to God, PHP is now evolving. There is a new "declare" directive (you can see it in above example) which now enforce type strictness.

declare(strict_types=1);

Just add this one line on top of your php file and PHP with yell and stop working Whenever you pass value with different type to a function/method. Off course, You need to define scalar types and return types first. I will discuss it in details soon in another blog post.

But How will it affect Typed Properties?

Without strict type, PHP always try to coerce type to desired one. Consider below situation

<?php
class Student {
    public int $score;
}

We did not declare strict type, so php will try to coerce type whenever we try to assign value to score.

$student = new Student();

$student->score = 44;
var_dump($student);
// int(44);

$student->score = "75";
// PHP will turn string "75" to int 75
var_dump($student);
// int(75);

$student->score = 56.7;
// PHP will turn float 56.7 to int 56
var_dump($student);
// int(56);

$student->score = "Invalid";
// Throws TypeError as PHP can't convert this string to integer

But when we declare strict type, PHP no longer allows invalid types

<?php
declare(strict_types=1);

class Student {
    public int $score;
}

$student = new Student();

$student->score = 44;
var_dump($student);
// int(44);

$student->score = "75";
var_dump($student);
// Throws TypeError as PHP no longer tries to coerce type string to int

$student->score = 56.7;
var_dump($student);
// Throws TypeError as PHP no longer tries to coerce type float to int



Type Behavior for inherited properties

There are only two rule for properties that has been inherited from parent class.

Non-Private Property: You can't change type of this property in child class. Property Type in child class must be same as parent class in this case.

class ParentClass {
    public string $name;
}

class ChildClass extends ParentClass {
    public string $name; // Allowed
}

class AnotherChildClass extends ParentClass {
    public int $name; // Not Allowed
}

Private Property: If the parent property is private, then the type may be changed arbitrarily.

class ParentClass {
    private string $name;
}

class ChildClass extends ParentClass {
    private int $name; // Allowed
}


Default Value for Typed Properties

Previously, Whenever you define a property in class without defining a default value, PHP would assign null to that property as default value. But typed properties are different. Properties that do not have a default value are considered "uninitialized".

class Student {
    public int $score; // Score is in uninitialized state
}

$student = new Student;
var_dump($student);

// object(Student)#1 (0) { ["score"]=> uninitialized(int) }

As we can see, score is uninitialized if you don't define any default value.

Another Important behavior is, You can't access a property before initializing it first. Accessing a uninitialized property generates a TypeError.

class Student {
    public int $score; // Score is in uninitialized state
}

$student = new Student;

var_dump($student->score);
// Fatal error: Uncaught Error: Typed property Student::$name must not be accessed before initialization

To be able to access/read a property, you have to either assign default value to it or assign value to it before accessing it.


Property Reference

On Strict mode, reference of a typed property will have same type enforcement of typed property.

<?php
declare(strict_types=1);

class Student {
   public string $name ='John';
}

$student = new Student;
$x = &$student->name;

$x = 1; // assigning int

var_dump($student);
// Uncaught TypeError: Cannot assign int to reference held by property Student::$name of type string

On non-strict mode, PHP will try to coerce type if possible. Otherwise same TypeError will be thrown.


Union Types

Okay Typed properties are great and all, but I have to admit real world is not follow so much happy path. Production Codes are (always) consists of full of methods that returns different type of value based on bussines logic. Same method may return array if row found in DB or false if no row found. As PHP only got usable scalar type and return type couple release back, Most codes written before that tends to ignore type system whenever possible.

So, We now feel the need of being able to define multiple type on properties. And fear not sailor, Union type is on its way. Hopefully we will see it in PHP 8.

https://wiki.php.net/rfc/union_types_v2


Actually, There are much more to explore in Typed Properties. As all new & shiny things in tech world, It will be more mature & robust by time and time only. If you are starting to code new projects now, there are no reason to not to use typed properties. Take benefit of type strictness in your project. Your future-self will thank you.