PHP: Was ist der Unterschied zwischen public, private und protected?
Die Tabelle dazu findet man in jedem Tutorial, und sie stimmt auch. Sie legt
aber eine Sache nahe, die falsch ist: dass private sich auf das einzelne
Objekt bezieht. Das tut es nicht, und das ist gut so.
Die Grundlagen
<?php
class Basis {
public string $oeffentlich = 'public';
protected string $geschuetzt = 'protected';
private string $privat = 'private';
}
?>
Von außen:
$b->oeffentlich : 'public'
$b->geschuetzt : Error: Cannot access protected property Basis::$geschuetzt
$b->privat : Error: Cannot access private property Basis::$privat
Aus der Klasse selbst heraus geht alles:
vonInnen() : ["public","protected","private"]
Beachte, dass es sich um einen Error handelt, nicht um eine Warnung. Der
Zugriff bricht also ab — anders als bei einer Eigenschaft, die es gar nicht
gibt.
In einer abgeleiteten Klasse
Und hier wird es schon interessanter:
Warning: Undefined property: Kind::$privat
ausDerAbleitung() : {"oeffentlich":"public","geschuetzt":"protected","privat":null}
protected funktioniert, private nicht. So weit erwartbar. Interessant ist
aber die Art des Fehlers: Es ist kein Zugriffsfehler, sondern eine
Warning: Undefined property.
Der Unterschied ist wichtig. private wird nicht vererbt — die Eigenschaft
existiert in Kind schlicht nicht. Deshalb bekommst du keinen Error, der
dein Skript anhält, sondern eine Warning und ein null, mit dem munter
weitergerechnet wird.
Das ist der gefährlichere von beiden Fällen.
Jetzt der Teil, der überrascht
private bezieht sich auf die Klasse, nicht auf das Objekt. Das heißt: Ein
Objekt darf auf die privaten Eigenschaften eines anderen Objekts derselben
Klasse zugreifen.
<?php
class Konto {
public function __construct(private int $stand) {}
public function vergleiche(Konto $anderes): string {
return $this->stand . ' gegen ' . $anderes->stand; // funktioniert!
}
}
?>
$a1->vergleiche($a2) : 100 gegen 250
$anderes->stand ist eine private Eigenschaft eines fremden Objekts, und
der Zugriff ist erlaubt.
Wenn man das zum ersten Mal sieht, wirkt es wie ein Loch in der Kapselung. Es
ist aber die Voraussetzung dafür, dass man überhaupt vernünftige Klassen
schreiben kann. Jede equals()-Methode, jeder Vergleich, jede
add()-Methode, die zwei Objekte derselben Klasse verrechnet, braucht genau
das:
<?php
class Geldbetrag {
public function __construct(private int $cent) {}
public function plus(Geldbetrag $anderer): self {
return new self($this->cent + $anderer->cent);
}
public function gleich(Geldbetrag $anderer): bool {
return $this->cent === $anderer->cent;
}
}
?>
Ohne diese Regel müsste Geldbetrag einen öffentlichen Getter für $cent
haben — und dann wäre die Kapselung erst recht dahin.
Bei protected gilt dasselbe, und zwar bis hinauf zur gemeinsamen
Basisklasse:
LinksKind liest Eltern-Instanz : 'ererbt'
LinksKind liest RechtsKind-Instanz : 'ererbt'
Eine Klasse darf also auch auf protected Eigenschaften einer
Geschwisterklasse zugreifen, solange beide von derselben Basis abstammen.
Der Fall, den man kennen sollte: __get()
Das ist der subtilste Punkt in diesem Artikel.
<?php
class Magisch {
private array $daten = ['a' => 1];
public function __get(string $n): mixed {
return $this->daten[$n] ?? null;
}
}
?>
$m->a : 1 [__get('a') wurde aufgerufen]
$m->daten : NULL [__get('daten') wurde aufgerufen]
$m->daten ist private. Normalerweise wäre der Zugriff von außen ein
Error. Weil aber ein __get() existiert, ruft PHP stattdessen dieses
auf — und das liefert hier null.
Aus einem lauten Fehler wird ein stiller falscher Wert. Genau deshalb sind
magische Methoden beim Debuggen so unangenehm: Ein Tippfehler im
Eigenschaftsnamen führt nicht zu einem Absturz, sondern zu einem null, das
irgendwo weiter unten Ärger macht.
Wenn du __get() verwendest, lass es bei unbekannten Namen eine Exception
werfen statt null zurückzugeben:
<?php
public function __get(string $n): mixed {
if (!array_key_exists($n, $this->daten)) {
throw new InvalidArgumentException("Unbekannte Eigenschaft: $n");
}
return $this->daten[$n];
}
?>
Lockern ja, verschärfen nein
Beim Überschreiben darf die Sichtbarkeit weiter werden, aber nicht enger:
<?php
class Basis { public function machWas(): void {} }
class Kind extends Basis {
protected function machWas(): void {} // Fatal Error
}
?>
Fatal error: Access level to Kind::machWas() must be public
(as in class Basis) in /verstoss.php on line 8
Das ergibt Sinn: Wer ein Basis-Objekt entgegennimmt, verlässt sich darauf,
dass machWas() aufrufbar ist. Wäre die Verschärfung erlaubt, könnte eine
Ableitung diese Zusage brechen.
Umgekehrt geht es: Aus protected darf in der Ableitung public werden.
Was man mit Konstruktor-Promotion bekommt
Seit PHP 8.0 lässt sich das alles im Konstruktor abkürzen:
<?php
class Server {
public function __construct(
public readonly string $name,
private int $portNr = 22,
) {}
public function port(): int { return $this->portNr; }
}
?>
$s->name : web01
$s->port() : 22
$s->portNr : Error: Cannot access private property Server::$portNr
$s->name = "neu" : Error: Cannot modify readonly property Server::$name
readonly ist dabei die interessante Ergänzung: Die Eigenschaft ist von außen
lesbar, aber nach dem Konstruktor nicht mehr änderbar. Damit braucht man für
unveränderliche Werte keinen Getter mehr — public readonly reicht.
Was von außen sichtbar ist
Zum Nachsehen, wenn man unsicher ist:
get_object_vars von aussen : ["oeffentlich"]
get_object_vars von innen : ["oeffentlich","geschuetzt","privat"]
get_object_vars() zeigt, was an der jeweiligen Stelle im Code erreichbar
ist. Dasselbe gilt für json_encode(), das nur die öffentlichen
Eigenschaften mitnimmt — was einen regelmäßig überrascht, wenn eine
API-Antwort plötzlich leer ist.
Was ich in der Praxis mache
- Eigenschaften grundsätzlich
private. Wenn eine Ableitung sie braucht, wird sie zuprotected— aber erst dann. - Methoden
public, wenn sie zur Schnittstelle gehören, sonstprivate. - Für unveränderliche Werte
public readonlystatt Getter. protectedsparsam. Es ist eine Zusage an alle künftigen Ableitungen und damit fast so bindend wiepublic.
Die Faustregel dahinter: Alles, was nicht private ist, kannst du später nicht
mehr ohne Weiteres ändern. Deshalb fängt man eng an und öffnet, wenn es nötig
wird.
Zusammenfassung
publicüberall,protectedin der Klasse und ihren Ableitungen,privatenur in der Klasse selbst.privateist klassenbezogen, nicht objektbezogen: Ein Objekt sieht die privaten Eigenschaften anderer Instanzen derselben Klasse.privatewird nicht vererbt — der Zugriff in einer Ableitung ist eine Warning mitnull, kein Error.- Ein vorhandenes
__get()ersetzt den Zugriffsfehler durch einen stillen Rückgabewert. - Die Sichtbarkeit lässt sich beim Überschreiben nur lockern.
public readonlyersetzt den klassischen Getter.
Hinweis zu Netcup (Werbung)
Der deutsche Hoster Netcup bietet unter anderem günstige und zugleich leistungsstarke Webhosting Pakete, KVM-basierte Root Server und dezidierte Server an. Mit unseren Gutscheincodes kannst du noch mehr Geld sparen (6€ bei deiner ersten Bestellung, 30% Rabatt auf alle KVM-basierten Root Server, ...).