Skip to content
Lesson 13 of 17

Step 1 of 7 · Reading · ~5 min

Learn

Classes, Inheritance, Errors

Classes and Objects

Every value you have handled so far has been a number, a string, or an array. A class lets you invent a new kind of value: a name for some data, together with the operations that belong with it. PHP has been object-oriented since PHP 5, and everything in the language above the level of a single script is built on classes.

Defining a class

<?php
class Parcel {
    public string $tracking;
    public int $grams;

    public function __construct(string $tracking, int $grams) {
        $this->tracking = $tracking;
        $this->grams = $grams;
    }

    public function label(): string {
        return "{$this->tracking} ({$this->grams}g)";
    }
}

$p = new Parcel('LX4471', 850);
echo $p->label(), "\n";
echo $p->tracking, "\n";

//> LX4471 (850g)
//> LX4471
PieceWhat it means
class Parcel { ... }defines the new type
public string $tracking;a typed property, declared in the class body (PHP 7.4+)
__constructruns automatically when you write new
$thisthe object the method was called on — note the $
->reaches a property or a method
::reaches a static member instead

Declare the property in the class body, then assign it in the constructor. Those two steps are the shape every exercise in this chapter expects.

Visibility: what the outside world may touch

<?php
class Depot {
    private int $capacity;
    public string $city;

    public function __construct(string $city, int $capacity) {
        $this->city = $city;
        $this->capacity = $capacity;
    }

    public function accept(int $parcels): bool {
        if ($parcels > $this->capacity) {
            return false;
        }
        $this->capacity -= $parcels;
        return true;
    }

    public function remaining(): int {
        return $this->capacity;
    }
}

$d = new Depot('Lisbon', 100);
var_dump($d->accept(30));
var_dump($d->accept(200));
echo $d->remaining(), "\n";
echo $d->city, "\n";

//> bool(true)
//> bool(false)
//> 70
//> Lisbon
ModifierWho can reach it
publicanyone, from anywhere
protectedthis class and any class extending it
privatethis class only

The reason $capacity is private shows up in accept(): the only route to changing it is a method that checks first, so the depot can never be over-filled. Reach past that from outside and PHP refuses — reading $d->capacity at the top level stops the program with Error: Cannot access private property Depot::$capacity.

Default to private for properties and let methods decide what callers may do. That is not ceremony; it is the only way to keep a rule like "capacity never goes negative" true.

The PHP 8 shorthand you will meet in real code

Grader note: PHP 8.0 added constructor property promotionpublic function __construct(private int $cents) {} declares the property and assigns it in a single stroke — and PHP 8.1 added readonly for write-once properties. Both are parse errors on this course's PHP 7.4 grader, which answers syntax error, unexpected 'private' (T_PRIVATE), expecting variable (T_VARIABLE) and then runs none of your file. Learn to read them, because modern codebases are full of them; write the two-step form shown above in the exercises here.

Static members belong to the class

<?php
class Scanner {
    private static int $scans = 0;

    public static function record(): void {
        self::$scans++;
    }

    public static function total(): int {
        return self::$scans;
    }
}

Scanner::record();
Scanner::record();
Scanner::record();
echo Scanner::total(), "\n";

//> 3
  • static puts the member on the class, so there is exactly one copy however many objects exist.
  • self:: means "the class this code is written in". A static property keeps its dollar sign after it: self::$scans.
  • A static method has no $this, because it was never called on an object.

Class constants

<?php
class Tier {
    const STANDARD = 'standard';
    const EXPRESS  = 'express';
    const SURCHARGE = 9;
}

echo Tier::EXPRESS, "\n";
echo Tier::SURCHARGE + 1, "\n";

//> express
//> 10

A constant carries no dollar sign, can never be reassigned, and is read with :: from anywhere. On PHP 7.4 this is how you write a fixed set of options. PHP 8.1 added real enum types; enum is not a keyword at all on this grader, so an enum declaration is a parse error here.

__toString: using an object where a string is expected

<?php
class Money {
    private int $cents;

    public function __construct(int $cents) {
        $this->cents = $cents;
    }

    public function __toString(): string {
        return sprintf('%.2f EUR', $this->cents / 100);
    }
}

$m = new Money(12345);
echo $m, "\n";
echo "total: $m\n";

//> 123.45 EUR
//> total: 123.45 EUR

__toString() runs whenever the object lands in a string context — echo, interpolation, concatenation. It has to return a string; returning anything else is a fatal error. Note what this buys: Money stores an exact integer number of cents and never touches a float, yet it still prints like money.

Common mistakes

  • Forgetting $this-> — a bare $grams inside a method is a local variable that merely shares a name with your property.
  • Using . where -> belongs. concatenates strings, so $p.label() becomes a puzzle instead of a method call.
  • Using => where -> belongs=> builds array entries and has nothing to do with objects.
  • Using -> on a static member — statics want Scanner::total() from outside and self::$scans from inside.
  • Assigning to a property you never declared — PHP 7.4 allows it, quietly creating an untyped property and discarding the checking you wrote the class body for.

Your exercise

Rectangle Class wants a Rectangle holding private int $width and int $height, a constructor that stores both, an area() returning their product, and a perimeter() returning 2 * ($width + $height). The starter already reads the two numbers and leaves the object lines commented out for you.

The mistake the grader catches is the PHP 8 shorthand: __construct(private int $width, private int $height) {} is a parse error here, so the program never starts and all three tests fail at once. Declare both properties in the class body and assign them with $this->width = $width;. The second trap is the output shape — two lines, area: 15 then perimeter: 16 for the first test's 5 by 3 rectangle, lower-case labels, one space after each colon, and a "\n" closing both of them.

Up nextInheritance: extends, abstract, finalClasses, Inheritance, Errors

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…