objects namespaces and application architecture
Late Static Bindings
self:: means "the class where this method is written." static:: means "the class that was used to make the call." That difference matters in inheritance, named constructors, and static factory methods.
self:: Binds Early
self::class refers to the class where the code is defined.
<?php
declare(strict_types=1);
class BaseModel
{
public static function selfName(): string
{
return self::class;
}
}
final class UserModel extends BaseModel
{
}
echo UserModel::selfName() . PHP_EOL;
// Prints:
// BaseModel
Even though the method was called through UserModel, self::class still points to BaseModel.
static:: Binds Late
static::class uses the class that was actually called.
<?php
declare(strict_types=1);
class BaseModel
{
public static function staticName(): string
{
return static::class;
}
}
final class UserModel extends BaseModel
{
}
echo UserModel::staticName() . PHP_EOL;
// Prints:
// UserModel
That is late static binding. PHP waits until runtime to decide what static:: means.
new static() In Named Constructors
Late static binding is often used in named constructors on base classes.
<?php
declare(strict_types=1);
class Identifier
{
public function __construct(
public readonly string $value,
) {
if ($value === '') {
throw new InvalidArgumentException('Identifier cannot be empty.');
}
}
public static function fromString(string $value): static
{
return new static(trim($value));
}
}
final class OrderId extends Identifier
{
}
$id = OrderId::fromString(' ORD-123 ');
echo $id::class . ': ' . $id->value . PHP_EOL;
// Prints:
// OrderId: ORD-123
Because the method returns new static(), calling OrderId::fromString() returns an OrderId, not just an Identifier.
Constants And Static Properties
Late static binding can also affect constants and static properties.
<?php
declare(strict_types=1);
class Report
{
protected const TYPE = 'generic';
public static function type(): string
{
return static::TYPE;
}
}
final class SalesReport extends Report
{
protected const TYPE = 'sales';
}
echo SalesReport::type() . PHP_EOL;
// Prints:
// sales
If Report::type() used self::TYPE, the result would be generic.
Risks Of new static()
new static() assumes child classes can be constructed the same way as the parent. That can break if a child class adds required constructor arguments.
<?php
declare(strict_types=1);
class BaseRecord
{
public function __construct(
public string $id,
) {
}
public static function make(string $id): static
{
return new static($id);
}
}
This is only safe if subclasses keep a compatible constructor. If subclasses need different construction rules, prefer explicit factory methods on each concrete class.
When To Use It
Late static binding is useful for:
- inherited named constructors that should return the child class
- base classes that expose overridden constants
- framework base classes and active-record style models
- fluent APIs that need to preserve the concrete type
It is not needed in most ordinary application services. If inheritance is not involved, static:: usually adds confusion without benefit.
What You Should Be Able To Do
After this lesson, you should be able to explain the difference between self:: and static::, understand why new static() can return a subclass, and recognise the constructor compatibility risk.
For junior work, the practical skill is reading framework and base-class code that uses static:: without confusing it with a normal static method call.
Practice
Practice: Compare `self::` And `static::`
Task
Build:
- a base identifier class with
selfName()andstaticName() - a child identifier class
- a named constructor that returns
static
Print the result of calling both name methods through the child class, then create a child identifier through the inherited named constructor.
Use strict types. Keep the expected output in the PHP code block as printed lines or comments.
Check Your Work
Confirm:
self::classpoints at the base classstatic::classpoints at the called child class- the named constructor returns the child type
Afterward, explain when new static() can be risky.
Show solution
<?php
declare(strict_types=1);
class Identifier
{
public function __construct(
public readonly string $value,
) {
}
public static function selfName(): string
{
return self::class;
}
public static function staticName(): string
{
return static::class;
}
public static function fromString(string $value): static
{
return new static(trim($value));
}
}
final class OrderId extends Identifier
{
}
echo OrderId::selfName() . PHP_EOL;
echo OrderId::staticName() . PHP_EOL;
$id = OrderId::fromString(' ORD-123 ');
echo $id::class . ': ' . $id->value . PHP_EOL;
// Prints:
// Identifier
// OrderId
// OrderId: ORD-123
new static() can be risky if a child class changes the constructor signature. The inherited factory would still call new static($value), which may no longer be enough to build the child object.