SmartObject
SmartObject a amélioré pendant des années le comportement des objets en PHP. Depuis PHP 8.4, toutes ses fonctionnalités font partie intégrante de PHP, accomplissant ainsi sa mission historique de pionnier de l'approche orientée objet moderne en PHP.
Installation :
composer require nette/utils
SmartObject est né en 2007 comme une solution révolutionnaire aux limitations du modèle objet de PHP de l'époque. À une période où PHP souffrait de nombreux problèmes de conception orientée objet, il a apporté des améliorations significatives et simplifié le travail des développeurs. Il est devenu une partie légendaire du framework Nette. Il proposait des fonctionnalités que PHP n'obtiendrait que de nombreuses années plus tard – de la validation d'accès aux propriétés jusqu'à la gestion sophistiquée des erreurs. Avec l'arrivée de PHP 8.4, il a achevé sa mission historique, car toutes ses fonctionnalités sont devenues des parties natives du langage. Il était en avance sur le développement de PHP de 17 années remarquables.
SmartObject a connu une évolution technique intéressante. Initialement, il était implémenté comme une classe
Nette\Object
, dont les autres classes héritaient les fonctionnalités nécessaires. Un changement majeur est survenu
avec PHP 5.4, qui a introduit le support des traits. Cela a permis sa transformation en trait Nette\SmartObject
,
apportant plus de flexibilité – les développeurs pouvaient utiliser ses fonctionnalités même dans les classes qui
héritaient déjà d'une autre classe. Alors que la classe originale Nette\Object
a cessé d'exister avec PHP 7.2
(qui interdisait de nommer les classes avec le mot ‘Object’), le trait Nette\SmartObject
continue d'exister.
Examinons les fonctionnalités que Nette\Object
et plus tard Nette\SmartObject
offraient. Chacune de
ces fonctions représentait à l'époque une avancée significative dans la programmation orientée objet en PHP.
États d'erreur cohérents
L'un des problèmes les plus pressants du PHP initial était le comportement incohérent lors du travail avec les objets.
Nette\Object
a apporté ordre et prévisibilité dans ce chaos. Voici comment PHP se comportait à l'origine :
echo $obj->undeclared; // E_NOTICE, plus tard E_WARNING
$obj->undeclared = 1; // passe silencieusement sans avertissement
$obj->unknownMethod(); // Fatal error (non capturable par try/catch)
Une erreur fatale arrêtait l'application sans possibilité de réaction. L'écriture silencieuse dans des membres non
existants sans avertissement pouvait conduire à des erreurs graves difficiles à détecter. Nette\Object
capturait
tous ces cas et lançait une exception MemberAccessException
, permettant aux programmeurs de réagir et de gérer ces
erreurs :
echo $obj->undeclared; // lance Nette\MemberAccessException
$obj->undeclared = 1; // lance Nette\MemberAccessException
$obj->unknownMethod(); // lance Nette\MemberAccessException
Depuis PHP 7.0, le langage ne provoque plus d'erreurs fatales non capturables, et depuis PHP 8.2, l'accès aux membres non déclarés est considéré comme une erreur.
Assistant « Did you mean ? »
Nette\Object
est venu avec une fonctionnalité très pratique : des suggestions intelligentes pour les fautes de
frappe. Quand un développeur faisait une erreur dans le nom d'une méthode ou d'une variable, il ne signalait pas seulement
l'erreur mais proposait aussi une aide en suggérant le nom correct. Ce message iconique, connu sous le nom de « did you mean ?
», a fait gagner aux programmeurs des heures de recherche de fautes de frappe :
class Foo extends Nette\Object
{
public static function from($var)
{
}
}
$foo = Foo::form($var);
// lance Nette\MemberAccessException
// "Call to undefined static method Foo::form(), did you mean from()?"
Bien que PHP lui-même n'ait aucune forme de « did you mean ? », cette fonctionnalité est maintenant fournie par Tracy. Il peut même auto-corriger ces erreurs.
Propriétés avec accès contrôlé
Une innovation significative que SmartObject a apportée à PHP était les propriétés avec accès contrôlé. Ce concept, courant dans des langages comme C# ou Python, permettait aux développeurs de contrôler élégamment l'accès aux données d'objet et d'assurer leur cohérence. Les propriétés sont un outil puissant de la programmation orientée objet. Elles fonctionnent comme des variables mais sont en réalité représentées par des méthodes (getters et setters). Cela permet la validation des entrées ou la génération de valeurs au moment de la lecture.
Pour utiliser les propriétés, vous devez :
- Ajouter l'annotation
@property <type> $xyz
à la classe - Créer un getter nommé
getXyz()
ouisXyz()
, un setter nommésetXyz()
- Vous assurer que le getter et le setter sont public ou protected. Ils sont optionnels – peuvent donc exister comme propriétés read-only ou write-only
Voyons un exemple pratique avec la classe Circle, où nous utiliserons les propriétés pour garantir que le rayon est toujours
non négatif. Nous remplacerons public $radius
par une propriété :
/**
* @property float $radius
* @property-read bool $visible
*/
class Circle
{
use Nette\SmartObject;
private float $radius = 0.0; // pas public !
// getter pour la propriété $radius
protected function getRadius(): float
{
return $this->radius;
}
// setter pour la propriété $radius
protected function setRadius(float $radius): void
{
// nous assainissons la valeur avant de la sauvegarder
$this->radius = max(0.0, $radius);
}
// getter pour la propriété $visible
protected function isVisible(): bool
{
return $this->radius > 0;
}
}
$circle = new Circle;
$circle->radius = 10; // appelle en réalité setRadius(10)
echo $circle->radius; // appelle getRadius()
echo $circle->visible; // appelle isVisible()
Depuis PHP 8.4, la même fonctionnalité peut être réalisée en utilisant les property hooks, qui offrent une syntaxe beaucoup plus élégante et concise :
class Circle
{
public float $radius = 0.0 {
set => max(0.0, $value);
}
public bool $visible {
get => $this->radius > 0;
}
}
Extension Methods
Nette\Object
a apporté un autre concept intéressant à PHP inspiré des langages de programmation modernes –
les extension methods. Cette fonctionnalité, empruntée à C#, permettait aux développeurs d'étendre élégamment les classes
existantes avec de nouvelles méthodes sans les modifier ni en hériter. Par exemple, vous pouviez ajouter une méthode
addDateTime()
à un formulaire qui ajoute un DateTimePicker personnalisé :
Form::extensionMethod(
'addDateTime',
fn(Form $form, string $name) => $form[$name] = new DateTimePicker,
);
$form = new Form;
$form->addDateTime('date');
Les extension methods se sont révélées peu pratiques car les éditeurs ne suggéraient pas leurs noms et signalaient au contraire que la méthode n'existait pas. Par conséquent, leur support a été abandonné. Aujourd'hui, il est plus courant d'utiliser la composition ou l'héritage pour étendre les fonctionnalités des classes.
Obtention du nom de la classe
SmartObject proposait une méthode simple pour obtenir le nom de la classe :
$class = $obj->getClass(); // avec Nette\Object
$class = $obj::class; // depuis PHP 8.0
Accès à la réflexion et aux annotations
Nette\Object
fournissait un accès à la réflexion et aux annotations via les méthodes
getReflection()
et getAnnotation()
. Cette approche simplifiait significativement le travail avec les
méta-informations des classes :
/**
* @author John Doe
*/
class Foo extends Nette\Object
{
}
$obj = new Foo;
$reflection = $obj->getReflection();
$reflection->getAnnotation('author'); // renvoie 'John Doe'
Depuis PHP 8.0, il est possible d'accéder aux méta-informations via les attributs, qui offrent encore plus de possibilités et un meilleur contrôle de type :
#[Author('John Doe')]
class Foo
{
}
$obj = new Foo;
$reflection = new ReflectionObject($obj);
$reflection->getAttributes(Author::class)[0];
Getters de méthodes
Nette\Object
offrait une façon élégante de passer des méthodes comme s'il s'agissait de variables :
class Foo extends Nette\Object
{
public function adder($a, $b)
{
return $a + $b;
}
}
$obj = new Foo;
$method = $obj->adder;
echo $method(2, 3); // 5
Depuis PHP 8.1, vous pouvez utiliser la first-class callable syntax, qui pousse ce concept encore plus loin :
$obj = new Foo;
$method = $obj->adder(...);
echo $method(2, 3); // 5
Événements
SmartObject offre une syntaxe simplifiée pour travailler avec les événements. Les événements permettent aux objets d'informer les autres parties de l'application des changements de leur état :
class Circle extends Nette\Object
{
public array $onChange = [];
public function setRadius(float $radius): void
{
$this->onChange($this, $radius);
$this->radius = $radius;
}
}
Le code $this->onChange($this, $radius)
est équivalent à la boucle suivante :
foreach ($this->onChange as $callback) {
$callback($this, $radius);
}
Pour plus de clarté, nous recommandons d'éviter la méthode magique $this->onChange()
. Un remplacement
pratique est la fonction Nette\Utils\Arrays::invoke :
Nette\Utils\Arrays::invoke($this->onChange, $this, $radius);