PHP erlaubt es Entwicklern, Konstruktormethoden für Klassen zu deklarieren.
Klassen mit Konstruktormethoden rufen diese für jedes neu erzeugte Objekt
auf, sodass Konstruktoren für alle Initialisierungen passend sind, die das
Objekt brauchen könnte, bevor es benutzt wird.
Hinweis:
Konstruktoren von Elternklassen werden nicht implizit aufgerufen, wenn
die Kindklasse einen Konstruktor definiert. Um einen Elternkonstruktor zu
benutzen, ist ein Aufruf von parent::__construct()
innerhalb des Kindkonstruktors notwendig. Falls das Kind keinen
Konstruktor definiert, kann er von der Elternklasse genau wie eine
normale Klassenmethode geerbt werden (falls er nicht als privat
deklariert wurde).
Beispiel #1 Konstruktoren bei der Vererbung
<?php class BaseClass { function __construct() { print "Im BaseClass-Konstruktor\n"; } }
class SubClass extends BaseClass { function __construct() { parent::__construct(); print "Im SubClass-Konstruktor\n"; } }
class OtherSubClass extends BaseClass { // erbt den Konstruktor von BaseClass }
// Im BaseClass-Konstruktor $obj = new BaseClass();
// Im BaseClass-Konstruktor // Im SubClass-Konstruktor $obj = new SubClass();
// Im BaseClass-Konstruktor $obj = new OtherSubClass(); ?>
Konstruktoren sind gewöhnliche Methoden, die bei der Instanziierung des
entsprechenden Objekts aufgerufen werden. Als solche können sie eine
beliebige Anzahl von Argumenten definieren, die erforderlich sein können,
einen Typ haben können und einen Standardwert haben können.
Konstruktorargumente werden aufgerufen, indem die Argumente in Klammern
hinter den Klassennamen gesetzt werden.
Beispiel #2 Die Verwendung von Argumenten eines Konstruktors
<?php class Punkt { protected int $x; protected int $y;
public function __construct(int $x, int $y = 0) { $this->x = $x; $this->y = $y; } }
// Beide Parameter übergeben. $p1 = new Punkt(4, 5); // Nur den erforderlichen Parameter übergeben. $y nimmt den Standardwert von 0 an. $p2 = new Punkt(4); // Mit benannten Parametern (von PHP 8.0 an): $p3 = new Punkt(y: 5, x: 4); ?>
Wenn eine Klasse keinen Konstruktor hat oder der Konstruktor keine
erforderlichen Argumente hat, können die Klammern weggelassen werden.
Konstruktoren alten Stils
Vor PHP 8.0.0 interpretieren Klassen im globalen Namensraum eine Methode,
die den gleichen Namen wie die Klasse trägt, als Konstruktor alten Stils.
Diese Syntax ist veraltet, und führt zu einem
E_DEPRECATED-Fehler, ruft die Funktion aber trotzdem
als Konstruktor auf. Wenn sowohl
__construct() als auch eine
Methode gleichen Namens definiert sind, wird
__construct() aufgerufen.
In Namensraum-Klassen, oder ab PHP 8.0.0 in jeder Klasse, hat eine
Methode mit dem Namen der Klasse keine besondere Bedeutung.
Ab PHP 8.0.0 können Konstruktorparameter auch so befördert werden, dass
sie einer Objekteigenschaft entsprechen. Sehr häufig werden
Konstruktorparameter einer Eigenschaft im Konstruktor zugewiesen, aber
ansonsten nicht bearbeitet. Die Konstruktor-Promotion bietet eine
Kurzform für diesen Anwendungsfall. Das obige Beispiel könnte wie folgt
umgeschrieben werden:
Beispiel #3 Die Verwendung der Beförderung von Konstruktor-Eigenschaften
<?php class Punkt { public function __construct(protected int $x, protected int $y = 0) { } }
Wenn ein Konstruktorargument einen Modifizierer enthält,
interpretiert PHP es sowohl als Objekteigenschaft als auch als
Konstruktorargument und weist der Eigenschaft den Wert des Arguments zu.
Der Konstruktorkörper kann dann leer sein oder andere Anweisungen
enthalten. Alle zusätzlichen Anweisungen werden ausgeführt, nachdem die
Argumentwerte den entsprechenden Eigenschaften zugewiesen wurden.
Nicht alle Argumente müssen befördert werden. Beförderte und nicht
beförderte Argumente können in beliebiger Reihenfolge zu kombiniert
werden. Beförderte Argumente haben keinen Einfluss auf den Code, der den
Konstruktor aufruft.
Hinweis:
Eigenschaften werden wahrscheinlich meist mit einem
Sichtbarkeitsmodifikator
(public, protected oder
private) befördert, aber jeder andere einzelne
Modifikator (z. B. readonly) hat denselben Effekt.
Hinweis:
Objekteigenschaften dürfen aufgrund der Mehrdeutigkeit, die in die
Engine einfließen würde, nicht mit callable typisiert
werden. Somit dürfen die angeführten Argumente auch nicht mit
callable typisiert werden. Jede andere
Typ-Deklaration ist
jedoch erlaubt.
Hinweis:
Da beförderte Eigenschaften sowohl zu einer Eigenschaft als auch zu
einem Funktionsparameter gemacht werden, gelten alle
Namensbeschränkungen sowohl für Eigenschaften als auch für Parameter.
Hinweis:
Attribute, die auf ein
befördertes Konstruktorargument gelegt werden, werden sowohl auf die
Eigenschaft als auch auf das Argument übertragen. Die Standardwerte für
ein befördertes Konstruktorargument werden nur auf das Argument und
nicht auf die Eigenschaft übertragen.
Ab PHP 8.1.0 können Objekte als Standardwerte für Parameter, als
statische Variablen und globale Konstanten sowie in
Eigenschaftsparametern verwendet werden. Objekte können nun auch an
define() übergeben werden.
Hinweis:
Es ist nicht zulässig, einen dynamischen Klassennamen, einen
Klassennamen, der keine Zeichenkette ist, oder eine anonyme Klasse zu
verwenden. Es ist auch nicht zulässig, Parameter zu entpacken, und nicht
unterstützte Ausdrücke als Parameter zu verwenden.
Beispiel #4 Verwendung von new bei Initialisierungen
<?php
// Alles zulässig: static $x = new Foo;
const C = new Foo;
function test($param = new Foo) {}
#[AnAttribute(new Foo)] class Test { public function __construct( public $prop = new Foo, ) {} }
// Alles nicht zulässig (Kompilierfehler): function test( $a = new (CLASS_NAME_CONSTANT)(), // dynamischer Klassenname $b = new class {}, // anonyme Klasse $c = new A(...[]), // Entpacken von Parametern $d = new B($abc), // nicht unterstützter Ausdruck ) {} ?>
PHP unterstützt nur einen einzigen Konstruktor pro Klasse. In einigen
Fällen, kann es jedoch wünschenswert sein, dass ein Objekt auf
verschiedene Weisen mit unterschiedlichen Eingaben erzeugt werden kann.
Der empfohlene Weg, dies zu tun, ist die, Konstruktoren in statische
Methoden zu packen.
Beispiel #5 Die Erzeugung über statische Methoden verwenden
Der Konstruktor kann private oder protected gemacht werden, um zu
verhindern, dass er von außen aufgerufen wird. Wenn dies der Fall ist,
kann nur eine statische Methode die Klasse instanziieren. Da sie in der
gleichen Klassendefinition enthalten ist, hat sie Zugriff auf private
Methoden, auch wenn sie nicht zur gleichen Objektinstanz gehören. Der
private Konstruktor ist optional und kann je nach Anwendungsfall sinnvoll
sein oder nicht.
Die drei public static Methoden veranschaulichen dann verschiedene
Möglichkeiten das Objekt zu instanziieren.
fromBasicData() nimmt genau die Parameter, die
benötigt werden, erzeugt dann das Objekt durch Aufruf des Konstruktors
und Rückgabe des Ergebnisses.
fromJson() akzeptiert eine JSON-Zeichenkette und
führt selbst eine Vorverarbeitung durch, um sie in das vom Konstruktor
gewünschte Format zu konvertieren. Sie gibt dann das neue Objekt
zurück.
fromXml() akzeptiert eine XML-Zeichenkette,
vorverarbeitet sie und erzeugt dann ein leeres Objekt. Der Konstruktor
wird immer noch aufgerufen, aber da alle Parameter optional sind,
überspringt sie die Methode. Sie weist dann den Objekteigenschaften
direkt Werte zu, bevor sie das Ergebnis zurückgibt.
In allen drei Fällen wird das Schlüsselwort static in den
Namen der Klasse übersetzt, in der sich der Code befindet. In diesem
Fall: Produkt.
PHP verfügt über ein Destruktorkonzept ähnlich dem anderer
objektorientierter Programmiersprachen wie C++. Die Destruktormethode wird
aufgerufen, sobald es keine weiteren Referenzen auf ein bestimmtes Objekt
mehr gibt, oder in beliebiger Reihenfolge am Ende des Skripts.
Beispiel #6 Destruktor-Beispiel
<?php
class MyDestructableClass { function __construct() { print "Im Konstruktor\n"; }
Wie Konstruktoren auch, werden Elterndestruktoren nicht implizit durch die
Engine aufgerufen. Um einen Elterndestruktor zu benutzen muss man explizit
die Funktion parent::__destruct() in der
Destruktorimplementierung aufrufen. Ebenso wie Konstruktoren kann eine
Kindklasse den Destruktor der Eltern erben, falls sie keinen eigenen
implementiert.
Der Destruktor wird aufgerufen, wenn das Skript mittels
exit() abgebrochen wird. Wenn exit()
innerhalb eines Destruktors aufgerufen wird, verhindert das die Ausführung
von jeglichen weiteren Shutdown-Routinen.
Wenn ein Destruktor neue Referenzen auf sein Objekt erzeugt, wird er nicht
ein zweites Mal aufgerufen, wenn die Anzahl der Referenzen wieder Null
erreicht oder während der Abschaltsequenz.
Seit PHP 8.4.0 werden, wenn die
Sammlung zyklischer Referenzen
während der Ausführung einer Fiber
stattfindet, die Destruktoren von Objekten, die für die Sammlung vorgesehen
sind, in einer separaten Fiber namens gc_destructor_fiber
ausgeführt.
Wenn diese Fiber unterbrochen wird, wird eine neue erstellt, um alle
verbleibenden Destruktoren auszuführen.
Die vorherige gc_destructor_fiber wird vom Garbage
Collector nicht mehr referenziert und kann, wenn sie nicht anderweitig
referenziert wird, eingesammelt werden.
Objekte, deren Destruktor ausgesetzt ist, werden nicht eingesammelt, bis
der Destruktor zurückgibt oder die Fiber selbst eingesammelt wird.
Hinweis:
Der Destruktor wird während der Skript-Abschaltung aufgerufen, weshalb die
Header immer bereits gesendet sind. Das aktuelle Verzeichnis während der
Beendigungsphase des Skripts kann bei einigen SAPIs (z. B. Apache)
ein anderes sein.
Hinweis:
Der Versuch, eine Exception aus einem Destruktor (der am Ende des Skripts
aufgerufen wird) heraus zu auszulösen, führt zu einem fatalen Fehler.
Be aware of potential memory leaks caused by circular references within objects. The PHP manual states "[t]he destructor method will be called as soon as all references to a particular object are removed" and this is precisely true: if two objects reference each other (or even if one object has a field that points to itself as in $this->foo = $this) then this reference will prevent the destructor being called even when there are no other references to the object at all. The programmer can no longer access the objects, but they still stay in memory.
Consider the following example:
<?php
header("Content-type: text/plain");
class Foo {
/** * An indentifier * @var string */ private $name; /** * A reference to another Foo object * @var Foo */ private $link;
public function __construct($name) { $this->name = $name; }
public function setLink(Foo $link){ $this->link = $link; }
public function __destruct() { echo 'Destroying: ', $this->name, PHP_EOL; } }
// create two Foo objects: $foo = new Foo('Foo 1'); $bar = new Foo('Foo 2');
// make them point to each other $foo->setLink($bar); $bar->setLink($foo);
// destroy the global references to them $foo = null; $bar = null;
// we now have no way to access Foo 1 or Foo 2, so they OUGHT to be __destruct()ed // but they are not, so we get a memory leak as they are still in memory. // // Uncomment the next line to see the difference when explicitly calling the GC: // gc_collect_cycles(); // // see also: http://www.php.net/manual/en/features.gc.php //
// create two more Foo objects, but DO NOT set their internal Foo references // so nothing except the vars $foo and $bar point to them: $foo = new Foo('Foo 3'); $bar = new Foo('Foo 4');
// destroy the global references to them $foo = null; $bar = null;
// we now have no way to access Foo 3 or Foo 4 and as there are no more references // to them anywhere, their __destruct() methods are automatically called here, // BEFORE the next line is executed:
echo 'End of script', PHP_EOL;
?>
This will output:
Destroying: Foo 3 Destroying: Foo 4 End of script Destroying: Foo 1 Destroying: Foo 2
But if we uncomment the gc_collect_cycles(); function call in the middle of the script, we get:
Destroying: Foo 2 Destroying: Foo 1 Destroying: Foo 3 Destroying: Foo 4 End of script
As may be desired.
NOTE: calling gc_collect_cycles() does have a speed overhead, so only use it if you feel you need to.
There are other advantages to using static factory methods to wrap object construction instead of bare constructor calls.
As well as allowing for different methods to use in different scenarios, with more relevant names both for the methods and the parameters and without the constructor having to handle different sets of arguments of different types:
* You can do all your input validation before attempting to construct the object. * The object itself can bypass that input validation when constructing new instances of its own class, since you can ensure that it knows what it's doing. * With input validation/preprocessing moved to the factory methods, the constructor itself can often be reduced to "set these properties to these arguments", meaning the constructor promotion syntax becomes more useful. * Having been hidden away from users, the constructor's signature can be a bit uglier without becoming a pain for them. Heh. * Static methods can be lifted and passed around as first class closures, to be called in the normal fashion wherever functions can be called, without the special "new" syntax. * The factory method need not return a new instance of that exact class. It could return a pre-existing instance that would do the same job as the new one would (especially useful in the case of immutable "value type" objects by reducing duplication); or a simpler or more specific subclass to do the job with less overhead than a more generic instance of the original class. Returning a subclass means LSP still holds.
The method will automatically be called externally to the instance. Declaring __destruct as protected or private will result in a warning and the magic method will not be called.
Note: In PHP 5.3.10 i saw strange side effects while some Destructors were declared as protected.
Being new to OOP, it took me quite a while to figure out that there are TWO underscores in front of the word __construct.
It is __construct Not _construct
Extremely obvious once you figure it out, but it can be sooo frustrating until you do.
I spent quite a bit of needless time debugging working code.
I even thought about it a few times, thinking it looked a little long in the examples, but at the time that just seemed silly(always thinking "oh somebody would have made that clear if it weren't just a regular underscore...")
All the manuals I looked at, all the tuturials I read, all the examples I browsed through - not once did anybody mention this!
(please don't tell me it's explained somewhere on this page and I just missed it, you'll only add to my pain.)
When a script is in the process of die()ing, you can't count on the order in which __destruct() will be called.
For a script I have been working on, I wanted to do transparent low-level encryption of any outgoing data. To accomplish this, I used a global singleton class configured like this:
public static function destroyAfter(&$obj) { self::getInstance()->objs[] =& $obj; /* Hopefully by forcing a reference to another object to exist inside this class, the referenced object will need to be destroyed before garbage collection can occur on this object. This will force this object's destruct method to be fired AFTER the destructors of all the objects referenced here. */ } public function __construct($key) { $this->C = new SimpleCrypt($key); ob_start(array($this,'getBuffer')); } public static function &getInstance($key=NULL) { if(!self::$_me && $key) self::$_me = new EncryptedComms($key); else return self::$_me; }
public function __destruct() { ob_end_flush(); }
public function getBuffer($str) { return $this->C->encrypt($str); }
}
In this example, I tried to register other objects to always be destroyed just before this object. Like this:
class A {
public function __construct() { EncryptedComms::destroyAfter($this); } }
One would think that the references to the objects contained in the singleton would be destroyed first, but this is not the case. In fact, this won't work even if you reverse the paradigm and store a reference to EncryptedComms in every object you'd like to be destroyed before it.
In short, when a script die()s, there doesn't seem to be any way to predict the order in which the destructors will fire.
*<Double post> I can't edit my previous note to elaborate on modifiers. Please excuse me.*
If both parent and child classes have a method with the same name defined, and it is called in parent's constructor, using `parent::__construct()` will call the method in the child.
<?php
class A { public function __construct() { $this->method(); } public function method() { echo 'A' . PHP_EOL; } } class B extends A { public function __construct() { parent::__construct(); } } class C extends A { public function __construct() { parent::__construct(); } public function method() { echo 'C' . PHP_EOL; } } $b = new B; // A $c = new C; // C
?>
In this example both A::method and C::method are public.
You may change A::method to protected, and C::method to protected or public and it will still work the same.
If however you set A::method as private, it doesn't matter whether C::method is private, protected or public. Both $b and $c will echo 'A'.
i have written a quick example about the order of destructors and shutdown functions in php 5.2.1:
<?php class destruction { var $name;
function destruction($name) { $this->name = $name; register_shutdown_function(array(&$this, "shutdown")); }
function shutdown() { echo 'shutdown: '.$this->name."\n"; }
function __destruct() { echo 'destruct: '.$this->name."\n"; } }
$a = new destruction('a: global 1');
function test() { $b = new destruction('b: func 1'); $c = new destruction('c: func 2'); } test();
$d = new destruction('d: global 2');
?>
this will output: shutdown: a: global 1 shutdown: b: func 1 shutdown: c: func 2 shutdown: d: global 2 destruct: b: func 1 destruct: c: func 2 destruct: d: global 2 destruct: a: global 1
conclusions: destructors are always called on script end. destructors are called in order of their "context": first functions, then global objects objects in function context are deleted in order as they are set (older objects first). objects in global context are deleted in reverse order (older objects last)
shutdown functions are called before the destructors. shutdown functions are called in there "register" order. ;)
Ensuring that instance of some class will be available in destructor of some other class is easy: just keep a reference to that instance in this other class.
/** * a funny example Mobile class * * @author Yousef Ismaeil Cliprz[At]gmail[Dot]com */
class Mobile {
/** * Some device properties * * @var string * @access public */ public $deviceName,$deviceVersion,$deviceColor;
/** * Set some values for Mobile::properties * * @param string device name * @param string device version * @param string device color */ public function __construct ($name,$version,$color) { $this->deviceName = $name; $this->deviceVersion = $version; $this->deviceColor = $color; echo "The ".__CLASS__." class is stratup.<br /><br />"; }
/** * Some Output * * @access public */ public function printOut () { echo 'I have a '.$this->deviceName .' version '.$this->deviceVersion .' my device color is : '.$this->deviceColor; }
/** * Umm only for example we will remove Mobile::$deviceName Hum not unset only to check how __destruct working * * @access public */ public function __destruct () { $this->deviceName = 'Removed'; echo '<br /><br />Dumpping Mobile::deviceName to make sure its removed, Olay :'; var_dump($this->deviceName); echo "<br />The ".__CLASS__." class is shutdown."; }
}
// Oh ya instance $mob = new Mobile('iPhone','5','Black');
// print output $mob->printOut();
?>
The Mobile class is stratup.
I have a iPhone version 5 my device color is : Black
Dumpping Mobile::deviceName to make sure its removed, Olay : string 'Removed' (length=7)
Please be aware of when using __destruct() in which you are unsetting variables...
Consider the following code: <?php class my_class { public $error_reporting = false;
function __construct($error_reporting = false) { $this->error_reporting = $error_reporting; }
function __destruct() { if($this->error_reporting === true) $this->show_report(); unset($this->error_reporting); } ?>
The above will result in an error: Notice: Undefined property: my_class::$error_reporting in my_class.php on line 10
It appears as though the variable will be unset BEFORE it actually can execute the if statement. Removing the unset will fix this. It's not needed anyways as PHP will release everything anyways, but just in case you run across this, you know why ;)
Peter has suggested using static methods to compensate for unavailability of multiple constructors in PHP. This works fine for most purposes, but if you have a class hierarchy and want to delegate parts of initialization to the parent class, you can no longer use this scheme. It is because unlike constructors, in a static method you need to do the instantiation yourself. So if you call the parent static method, you will get an object of parent type which you can't continue to initialize with derived class fields.
Imagine you have an Employee class and a derived HourlyEmployee class and you want to be able to construct these objects out of some XML input too.
<?php class Employee { public function __construct($inName) { $this->name = $inName; }
public static function constructFromDom($inDom) { $name = $inDom->name; return new Employee($name); }
private $name; }
class HourlyEmployee extends Employee { public function __construct($inName, $inHourlyRate) { parent::__construct($inName); $this->hourlyRate = $inHourlyRate; }
public static function constructFromDom($inDom) { // can't call parent::constructFromDom($inDom) // need to do all the work here again $name = $inDom->name; // increased coupling $hourlyRate = $inDom->hourlyrate; return new EmployeeHourly($name, $hourlyRate); }
private $hourlyRate; } ?>
The only solution is to merge the two constructors in one by adding an optional $inDom parameter to every constructor.