Skip to content

Constants and Static Members

A normal Property belongs to an Object. A Class Constant or Static Member belongs to the Class level. Choosing between them depends on whether the value is fixed, shared and mutable, or independent for every Object.

final class Order
{
public const STATUS_PENDING = 'pending';
private static int $createdCount = 0;
private string $status = self::STATUS_PENDING;
}
  • STATUS_PENDING is a fixed value associated with the Class.
  • $createdCount is one mutable value shared by the Class.
  • $status is independent State stored by every Order Object.

Constants have no $ when accessed. Static Properties do:

Order::STATUS_PENDING;
self::STATUS_PENDING;
self::$createdCount;
<?php
declare(strict_types=1);
final class Order
{
public const STATUS_PENDING = 'pending';
public const STATUS_PAID = 'paid';
private static int $createdCount = 0;
private string $status = self::STATUS_PENDING;
public function __construct()
{
self::$createdCount++;
}
public function markAsPaid(): void
{
$this->status = self::STATUS_PAID;
}
public function status(): string
{
return $this->status;
}
public static function createdCount(): int
{
return self::$createdCount;
}
}
$first = new Order();
$second = new Order();
$first->markAsPaid();
echo $first->status() . PHP_EOL; // paid
echo $second->status() . PHP_EOL; // pending
echo Order::createdCount(); // 2

The two Objects share the Class-level counter but retain independent status. Keeping both Properties private prevents callers from inventing a status or corrupting the counter.

These keywords select a Class when accessing a Class-level Member:

  • self:: selects the Class containing the expression.
  • static:: selects the Class from which the call began at runtime. This is Late Static Binding.
  • parent:: selects the direct Parent of the Class containing the expression.
class Payment
{
protected const TYPE = 'generic';
public static function usingSelf(): string
{
return self::TYPE;
}
public static function usingStatic(): string
{
return static::TYPE;
}
}
class CardPayment extends Payment
{
protected const TYPE = 'card';
public static function parentType(): string
{
return parent::TYPE;
}
}
echo CardPayment::usingSelf(); // generic
echo CardPayment::usingStatic(); // card
echo CardPayment::parentType(); // generic

The Child redeclares its own Constant; it does not mutate the Parent’s value. A final Constant prevents redeclaration. In a final Class, self:: is often clearer than static:: because no Child Class can benefit from late binding.

A small calculation with explicit inputs can be a reasonable Static Method:

final class PriceFormatter
{
public static function format(float $amount): string
{
return number_format($amount, 2) . ' EGP';
}
}

A checkout operation usually collaborates with payment, persistence, and notification Objects. Making that workflow static may hard-code or hide those Dependencies. An Object Service can receive them once and expose an Instance Method:

final class CheckoutService
{
public function __construct(
private PaymentGateway $paymentGateway,
private OrderRepository $orders,
private EmailSender $emailSender,
) {}
public function checkout(Order $order): void
{
// Coordinate the injected collaborators.
}
}

static is a PHP mechanism, not a synonym for a Service. Avoid shared mutable Static State when ordinary Object State or explicit collaboration represents the problem more clearly.

  • Treating a Static Property as independent State for every Object.
  • Writing self::count instead of self::$count for a Static Property.
  • Assuming self always refers to the first Parent Class.
  • Using static:: without needing late binding.
  • Exposing shared mutable State publicly.
  • Converting dependency-heavy Services into Static Methods for convenient access.

Constants are fixed Class-level values. Static Properties are mutable State shared at Class level. Instance Properties belong independently to Objects. self, static, and parent select different Classes during member access.

  1. Why is an Order status an Instance Property while a creation counter can be static?
  2. Why does a Static Property require $ but a Constant does not?
  3. When can self::TYPE and static::TYPE produce different results?
  4. Why can a dependency-heavy Static Service be difficult to replace and test?