Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Classes and Properties

A class defines objects with identity. Two variables can point to the same object, and a change through either variable changes that object.

class Counter {
  public int $value = 0;

  public function increment(): void {
    $this->value++;
  }
}

$first = new Counter();
$second = $first;
$second->increment();
write_line!($first->value); // 1

Declaring a class

A class body may contain properties, constants, and methods. Every member must state its visibility: public, protected, or private.

class User {
  public const string KIND = 'user';
  private static int $created = 0;

  public function __construct(public readonly int $id, private string $name) {
    self::$created++;
  }

  public function rename(string $name): void {
    $this->name = $name;
  }

  public function label(): string {
    return $this->id . ': ' . $this->name;
  }

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

Properties use $ in their names. Methods and constants do not. A property may share a name with a method or constant. Methods and constants may not share a name with each other. An enum case also uses that member name set.

Construction

new creates an object, then calls __construct when the class has one. The call site must have access to that constructor. Code outside the class can call only a public constructor.

final class Point {
  public function __construct(public int $x, public int $y) {}
}

$point = new Point(3, 4);

A visibility modifier on a constructor parameter promotes that parameter to a property. The property keeps the parameter’s type and readonly modifier.

Without promotion, the constructor assigns properties through $this:

final class Name {
  private string $value;

  public function __construct(string $value) {
    $this->value = $value;
  }

  public function value(): string {
    return $this->value;
  }
}

Named arguments work with constructors:

final class Entry {
  public function __construct(public int $number, public string $text) {}
}

$entry = new Entry(
  text: 'answer',
  number: 42,
);

A child constructor does not call its parent on its own. Call parent::__construct(...) when the parent needs setup.

A private constructor can force callers through a factory:

final class Token {
  private function __construct(public string $value) {}

  public static function from(string $value): Token {
    return new Token($value);
  }
}

$token = Token::from('ready');

Property state

A property may have a default value:

class Job {
  public string $state = 'waiting';
}

A property without a default starts uninitialized. Reading it throws Whim\Unwind\UninitializedPropertyError.

class Job {
  public string $state;
}

$job = new Job();
$state = $job->state; // throws

An initializer may call functions or methods and may create objects. Whim evaluates a non-static property initializer for each new object. Objects do not share that value.

final class Box {
  public function __construct(public int $value) {}
}

final class Holder {
  public Box $box = new Box(1);
}

$first = new Holder();
$second = new Holder();
$first->box->value = 9;
write_line!($second->box->value); // 1

Every write checks the property’s type. A compound assignment also leaves the old value in place when its operation or type check fails.

Readonly properties

A readonly property accepts one write. The write must occur in code that can access the property. Later writes throw Whim\Unwind\ReadonlyError.

class Account {
  public readonly int $id;

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

A child constructor may initialize an inherited public or protected readonly property. It may not initialize a private parent property.

readonly class makes each instance property readonly, including promoted properties that omit the word readonly.

readonly class Pair {
  public function __construct(public int $left, public int $right) {}
}

A readonly class cannot have static properties. A readonly class may extend only another readonly class, and a child of a readonly class must also be readonly.

Static properties and class constants

Static properties belong to the class family, not to one object. An inherited static property uses the same stored value.

class Registry {
  public static int $count = 0;
}

class ChildRegistry extends Registry {}

ChildRegistry::$count = 3;
write_line!(Registry::$count); // 3

A class constant has a type and a value:

final class Limits {
  public const int MAXIMUM = 100;
}

write_line!(Limits::MAXIMUM);

final prevents a child from replacing a class constant.

Constant values may call functions and methods or create objects. Whim checks them when it declares the class.

$this, self, parent, and static

$this is the current object in an instance method.

self names the class that declares the current method. parent names its direct parent. static names the class on which the call began, so it supports late static dispatch.

class Model {
  public static function create(): static {
    return new static();
  }

  public function kind(): string {
    return 'model';
  }
}

final class Post extends Model {
  public function kind(): string {
    return 'post';
  }
}

write_line!(Post::create()->kind()); // post

Use self, not static, as a parameter type. A return type may use either.

Abstract and final classes

An abstract class may hold abstract methods. An abstract method has no body. A concrete child must implement every abstract method before code can create it.

abstract class Shape {
  abstract public function area(): float;
}

final class Square extends Shape {
  public function __construct(public float $side) {}

  public function area(): float {
    return $this->side * $this->side;
  }
}

Code cannot create an abstract class. Code cannot extend a final class or replace a final method.

final abstract class has a narrow use: it groups static code. It may contain only constants, static properties, and concrete static methods.

final abstract class Numbers {
  public const int ONE = 1;

  public static function one(): int {
    return self::ONE;
  }
}

Cloning

clone!($object) creates a new object with copies of the source properties. The two objects then have separate property slots.

final class Point {
  public function __construct(public int $x, public int $y) {}
}

$source = new Point(1, 2);
$copy = clone!($source, y: 9);
$source->x = 7;

write_line!($copy->x); // 1
write_line!($copy->y); // 9

Named fields after the object replace properties on the clone. Normal visibility and readonly rules apply at the call site. A wrong field name or a non-object source throws TypeError. Some built-in classes reject cloning. Whim has no clone hook.

Destruction

A class may declare __destruct(): void. Whim calls it when no strong reference can reach the object, including when cycle collection finds an unreachable cycle.

final class Lease {
  public function __destruct(): void {
    write_line!('released');
  }
}

$lease = new Lease();
$lease = null;

A child inherits its parent’s destructor unless it declares one. A child destructor replaces the parent destructor; call parent::__destruct() to run both.

A destructor may throw. During normal execution, the throw starts at the statement that removed the last reference. During shutdown, a destructor failure becomes the program’s failure. Keep destructors short and make cleanup safe to call more than once.