PHP permet aux développeurs de déclarer des constructeurs pour
les classes. Les classes qui possèdent une méthode constructeur
appellent cette méthode à chaque création d'une nouvelle instance
de l'objet, ce qui est intéressant pour toutes les initialisations
dont l'objet a besoin avant d'être utilisé.
Note:
Les constructeurs parents ne sont pas appelés implicitement
si la classe enfant définit un constructeur. Si vous
voulez utiliser un constructeur parent, il sera nécessaire de faire
appel à parent::__construct() depuis le constructeur enfant.
Si l'enfant ne définit pas un constructeur alors, il peut être hérité
de la classe parente, exactement de la même façon qu'une méthode le serait
(si elle n'a pas été déclarée comme privée).
Exemple #1 Constructeur lors de l'héritage
<?php class BaseClass { function __construct() { print "Dans le constructeur de BaseClass\n"; } }
class SubClass extends BaseClass { function __construct() { parent::__construct(); print "Dans le constructeur de SubClass\n"; } }
class OtherSubClass extends BaseClass { // Constructeur hérité de BaseClass }
// Dans le constructeur de BaseClass $obj = new BaseClass();
// Dans le constructeur de BaseClass // Dans le constructeur de SubClass $obj = new SubClass();
// Dans le constructeur de BaseClass $obj = new OtherSubClass(); ?>
Les constructeurs sont des méthodes ordinaires qui sont appelées lors de
l'instanciation de leur objet correspondant. Par conséquent, ils peuvent
définir un nombre arbitraire d'arguments, qui peuvent être requis, avoir un type,
et peuvent avoir une valeur par défaut. Les arguments d'un constructeur sont appelés
en plaçant les arguments dans des parenthèses après le nom de la classe.
Exemple #2 Utiliser les arguments d'un constructeur
<?php class Point { protected int $x; protected int $y;
public function __construct(int $x, int $y = 0) { $this->x = $x; $this->y = $y; } }
// Pass both parameters. $p1 = new Point(4, 5); // Pass only the required parameter. $y will take its default value of 0. $p2 = new Point(4); // With named parameters (as of PHP 8.0): $p3 = new Point(y: 5, x: 4); ?>
Si une classe n'a pas de constructeur, ou que le constructeur n'a pas d'arguments requis,
les parenthèses peuvent être omises.
Style ancien des constructeurs
Antérieur à PHP 8.0.0, les classes dans le nom d'espace global interpréteront une
méthode qui a le même nom que la classe comme un constructeur de style ancien.
Cette syntaxe est obsolète, et résultera en une erreur E_DEPRECATED
mais appellera quand même cette fonction comme un constructeur.
Si __construct() et une méthode du même nom
sont définies, __construct() sera appelé.
Les classes dans les espaces de nom, ou toute classe à partir de PHP 8.0.0,
une méthode du même nom que la classe n'a jamais de signification particulière.
Toujours utiliser __construct() dans du nouveau code.
À partir de PHP 8.0.0, les paramètres du constructeur peuvent être promus pour
correspondre à une propriété de l'objet. Il est très commun pour les paramètres
d'un constructeur d'être assignés à une propriété sans effectuer d'opérations dessus.
La promotion du constructeur fournit un raccourci pour ce cas d'utilisation.
L'exemple ci-dessus peut être réécrit de la façon suivante.
Exemple #3 Utilisant la promotion de propriété de constructeur
<?php class Point { public function __construct(protected int $x, protected int $y = 0) { } }
Quand un argument de constructeur inclut un modificateur, PHP l'interprétera
comme une propriété d'objet et un argument du constructeur, et assigne la valeur de l'argument
à la propriété. Le corps du constructeur peut être alors vide ou peut contenir d'autres
déclarations. Toutes déclarations additionnelles seront exécutées après que la valeur de
l'argument a été assignée à sa propriété correspondante.
Tous les arguments ne doivent pas être promus. Il est possible de mélanger et assortir les
arguments promus et non-promus, dans n'importe quel ordre. Les arguments promus n'ont aucun impact
sur du code appellant le constructeur.
Note:
Utiliser un modificateur de visibilité
(public, protected ou private) est
la manière la plus probable d'appliquer la promotion de propriété, mais tout autre modificateur
unique (comme readonly) aura le même effet.
Note:
Les propriétés d'objet ne peuvent être typés callable à cause des ambiguïtés du moteur
que ceci introduirait. Ainsi, les arguments promus, ne peuvent pas non plus être typés
callable. Cependant, toute autre
déclaration de type est permise.
Note:
Comme les propriétés promues sont à la fois désucrées à une propriété
et aussi comme le paramètre d'une fonction, toutes les restrictions de
nommage pour les propriétés et les paramètres s'appliquent.
Note:
Les attributs placés sur un
argument de constructeur promu seront reproduits à la fois sur la
propriété et sur l'argument. Les valeurs par défaut sur un argument de
constructeur promu ne seront répliquées que sur l'argument et non sur la propriété.
À partir de PHP 8.1.0, les objets peuvent être utilisés comme valeur par défaut pour les paramètres,
variables statiques, constantes globales et les arguments d'attributs.
Les objets peuvent être désormais passés à define().
Note:
L'utilisation d'un nom de classe dynamique ou non-string ou une class anonyme n'est pas permise.
L'utilisation du déballage d'arguments n'est pas permise.
L'utilisation d'expressions non supportées en tant qu'argument n'est pas permise.
Exemple #4 Utilisation de new dans des initialiseurs
<?php
// Tous autorisés : 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, ) {} }
// All not allowed (compile-time error): function test( $a = new (CLASS_NAME_CONSTANT)(), // nom de classe dynamique $b = new class {}, // classe anonyme $c = new A(...[]), // déballage d'arguments $d = new B($abc), // expression de constante non supportée ) {} ?>
PHP supporte uniquement un constructeur unique par classe. Cependant, dans
certains cas il peut être désirable de permettre à un objet d'être construit
de manière différente avec des entrées différentes.
La façon recommandée de faire ceci est en utilisant des méthodes statiques
comme une enveloppe du constructeur.
Exemple #5 Utilisant la création via une méthode statique
Le constructeur peut être rendu privé ou protégé pour empêcher son appel
depuis l'extérieur. Dans ce cas, seul une méthode statique sera capable
d'instancier la classe. Puisqu'elles sont dans la même définition de classe,
elles ont accès aux méthodes privées, même dans une instance différente de
l'objet. Un constructeur privé est optionnel et peut ou pas faire de sens
en fonction du cas d'utilisation.
Les trois méthodes statiques publiques démontrent alors des manières différentes
d'instancier l'objet.
fromBasicData() prend les paramètres exacts qui sont nécessaires,
puis créé l'objet en appellant le constructeur puis retournant le résultat.
fromJson() accepte une chaîne de caractère JSON et fait
du pré-traitement sur lui même pour se convertir dans le format désiré par
le constructeur. Elle retourne alors le nouvel objet.
fromXml() accepte une chaîne de caractère XML, fait du pré-traitement,
puis créé un objet brut. Le constructeur est appelé, mais comme tous les
paramètres sont optionnels la méthode les ignore. Elle assigne alors les
valeurs aux propriétés de l'objet directement avant de retourner le résultat.
Dans les trois cas, le mot clé static est traduit en le nom de
la classe dont le code y est. Dans ce cas, Product.
PHP possède un concept de destructeur similaire à celui d'autres langages
orientés objet, comme le C++. La méthode destructeur est appelée
dès qu'il n'y a plus de référence sur un objet donné, ou dans n'importe quel
ordre pendant la séquence d'arrêt.
Exemple #6 Exemple avec un Destructeur
<?php
class MyDestructableClass { function __construct() { print "In constructor\n"; }
Tout comme le constructeur, le destructeur parent ne sera pas appelé
implicitement par le moteur. Pour exécuter le destructeur parent, vous
devez appeler explicitement la fonction
parent::__destruct dans le corps du destructeur.
Tout comme les constructeurs, une classe enfant peut hériter du
destructeur du parent s'il n'en implémente pas un lui même.
Le destructeur sera appelé même si l'exécution du script est stoppée
en utilisant la fonction exit().
Appeler la fonction exit()
dans un destructeur empêchera l'exécution des routines d'arrêt restantes.
Si un destructeur crée de nouvelles références à son objet, il ne sera pas
appelé une seconde fois lorsque le compteur de références atteint de
nouveau zéro ou durant la séquence d'arrêt.
À partir de PHP 8.4.0, lorsque la
collecte des cycles
se produit pendant l'exécution d'une
fibre, les destructeurs des objets
programmés pour la collecte sont exécutés dans une fibre distincte, appelée
gc_destructor_fiber.
Si cette fibre est suspendue, une nouvelle fibre sera créée pour exécuter
les destructeurs restants.
La précédente gc_destructor_fiber ne sera plus référencée
par le ramasse-miettes et pourra être collectée si elle n'est pas référencée
ailleurs.
Les objets dont le destructeur est suspendu ne seront pas collectés tant
que le destructeur n'a pas terminé ou que la fibre elle-même n'est pas
collectée.
Note:
Les destructeurs appelés durant l'arrêt du script sont dans une situation
où les en-têtes HTTP ont déjà été envoyés.
Le dossier de travail dans la phase d'arrêt du script
peut être différent avec certaines APIs (e.g. Apache).
Note:
Tenter de lancer une exception depuis un destructeur (appelé à la fin du script)
entraine une erreur fatale.
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.