SmartObject
SmartObject corrigeait le comportement des objets de plusieurs façons, mais le PHP d'aujourd'hui inclut déjà la plupart de ces améliorations de façon native. Cependant, il ajoute encore le support des propriétés.
Installation :
composer require nette/utils
Propriétés, getters et setters
Dans les langages modernes orientés objet (par exemple C#, Python, Ruby, JavaScript), le terme propriété fait référence aux membres spéciaux des classes qui ressemblent à des variables mais sont en fait représentés par des méthodes. Lorsque la valeur de cette “variable” est assignée ou lue, la méthode correspondante (appelée getter ou setter) est appelée. C'est très pratique, cela nous donne un contrôle total sur l'accès aux variables. Nous pouvons valider l'entrée ou générer des résultats uniquement lorsque la propriété est lue.
Les propriétés PHP ne sont pas prises en charge, mais le trait Nette\SmartObject
peut les imiter. Comment
l'utiliser ?
- Ajoutez une annotation à la classe sous la forme
@property <type> $xyz
- Créez un getter nommé
getXyz()
ouisXyz()
, un setter nommésetXyz()
- Le getter et le setter doivent être public ou protégé et sont optionnels, donc il peut y avoir une propriété lire seulement ou écrire seulement.
Nous utiliserons la propriété de la classe Circle pour nous assurer que seuls des nombres non négatifs sont placés dans la
variable $radius
. Remplacez public $radius
par la propriété :
/**
* @property float $radius
* @property-read bool $visible
*/
class Circle
{
use Nette\SmartObject;
private float $radius = 0.0; // not public
// getter for property $radius
protected function getRadius(): float
{
return $this->radius;
}
// setter for property $radius
protected function setRadius(float $radius): void
{
// sanitizing value before saving it
$this->radius = max(0.0, $radius);
}
// getter for property $visible
protected function isVisible(): bool
{
return $this->radius > 0;
}
}
$circle = new Circle;
$circle->radius = 10; // actually calls setRadius(10)
echo $circle->radius; // calls getRadius()
echo $circle->visible; // calls isVisible()
Les propriétés sont principalement du “sucre syntaxique” ((sucre syntaxique)), destiné à rendre la vie du programmeur plus douce en simplifiant le code. Si vous n'en voulez pas, vous n'êtes pas obligé de les utiliser.
Un aperçu de l'histoire
SmartObject permettait d'affiner le comportement des objets de nombreuses manières, mais le PHP d'aujourd'hui incorpore déjà la plupart de ces améliorations de manière native. Le texte suivant est un regard nostalgique sur l'histoire, nous rappelant comment les choses ont évolué.
Dès le début, le modèle objet de PHP a souffert d'une myriade de lacunes et de déficiences. Cela a conduit à la création
de la classe Nette\Object
(en 2007), qui visait à corriger ces problèmes et à améliorer le confort d'utilisation
de PHP. Il suffisait que d'autres classes héritent de cette classe pour bénéficier des avantages qu'elle offrait. Lorsque PHP
5.4 a introduit le support des traits, la classe Nette\Object
a été remplacée par le trait
Nette\SmartObject
. Cela a éliminé le besoin d'hériter d'un ancêtre commun. De plus, le trait pouvait être
utilisé dans des classes qui héritaient déjà d'une autre classe. La fin définitive de Nette\Object
est
intervenue avec la publication de PHP 7.2, qui a interdit aux classes d'être nommées Object
.
Au fur et à mesure du développement de PHP, le modèle d'objet et les capacités du langage se sont améliorés. Plusieurs
fonctions de la classe SmartObject
sont devenues redondantes. Depuis la sortie de PHP 8.2, il ne reste plus qu'une
seule fonctionnalité non directement supportée par PHP : la possibilité d'utiliser ce que l'on appelle des propriétés.
Quelles sont les fonctionnalités offertes par Nette\Object
et, par extension, par Nette\SmartObject
?
En voici un aperçu. (Dans les exemples, la classe Nette\Object
est utilisée, mais la plupart des fonctionnalités
s'appliquent également au trait Nette\SmartObject
).
Erreurs de cohérence
PHP avait un comportement incohérent lors de l'accès aux membres non déclarés. L'état au moment de
Nette\Object
était le suivant :
echo $obj->undeclared; // E_NOTICE, later E_WARNING
$obj->undeclared = 1; // passes silently without reporting
$obj->unknownMethod(); // Fatal error (not catchable by try/catch)
Une erreur fatale terminait l'application sans possibilité de réagir. L'écriture silencieuse dans des membres inexistants
sans avertissement pouvait entraîner des erreurs graves difficiles à détecter. Nette\Object
Tous ces cas ont été
détectés et une exception MemberAccessException
a été levée.
echo $obj->undeclared; // throw Nette\MemberAccessException
$obj->undeclared = 1; // throw Nette\MemberAccessException
$obj->unknownMethod(); // throw Nette\MemberAccessException
Depuis PHP 7.0, PHP ne provoque plus d'erreurs fatales non capturables, et l'accès à des membres non déclarés est un bug depuis PHP 8.2.
Voulez-vous dire ?
Si une erreur Nette\MemberAccessException
était déclenchée, peut-être à cause d'une faute de frappe lors de
l'accès à une variable d'objet ou de l'appel d'une méthode, Nette\Object
tentait de donner un indice dans le
message d'erreur sur la façon de corriger l'erreur, sous la forme de l'addendum emblématique “Did you mean ?”.
class Foo extends Nette\Object
{
public static function from($var)
{
}
}
$foo = Foo::form($var);
// throw Nette\MemberAccessException
// "Call to undefined static method Foo::form(), did you mean from()?"
Bien que le PHP d'aujourd'hui n'ait pas de fonction “did you mean ?”, cette phrase peut être ajoutée aux erreurs par Tracy. Il peut même corriger automatiquement ces erreurs.
Méthodes d'extension
Inspiré par les méthodes d'extension de C#. Elles donnaient la possibilité d'ajouter de nouvelles méthodes à des classes
existantes. Par exemple, vous pouvez ajouter la méthode addDateTime()
à un formulaire pour ajouter votre propre
DateTimePicker.
Form::extensionMethod(
'addDateTime',
fn(Form $form, string $name) => $form[$name] = new DateTimePicker,
);
$form = new Form;
$form->addDateTime('date');
Les méthodes d'extension se sont avérées peu pratiques car leurs noms n'étaient pas autocomplétés par les éditeurs, qui signalaient au contraire que la méthode n'existait pas. Par conséquent, leur prise en charge a été abandonnée.
Obtention du nom de la classe
$class = $obj->getClass(); // using Nette\Object
$class = $obj::class; // since PHP 8.0
Accès à la réflexion et aux annotations
Nette\Object
a proposé un accès à la réflexion et aux annotations en utilisant les méthodes
getReflection()
et getAnnotation()
:
/**
* @author John Doe
*/
class Foo extends Nette\Object
{
}
$obj = new Foo;
$reflection = $obj->getReflection();
$reflection->getAnnotation('author'); // returns 'John Doe'
Depuis PHP 8.0, il est possible d'accéder aux méta-informations sous forme d'attributs :
#[Author('John Doe')]
class Foo
{
}
$obj = new Foo;
$reflection = new ReflectionObject($obj);
$reflection->getAttributes(Author::class)[0];
Les récupérateurs de méthodes
Nette\Object
offraient un moyen élégant de traiter les 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 syntaxe:https://www.php.net/…lable_syntax dite first-class callable:
$obj = new Foo;
$method = $obj->adder(...);
echo $method(2, 3); // 5
Événements
Nette\Object
a offert un sucre syntaxique pour déclencher l'événement:
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 à ce qui suit :
foreach ($this->onChange as $callback) {
$callback($this, $radius);
}
Pour des raisons de clarté, nous vous recommandons d'éviter la méthode magique $this->onChange()
. Un bon
substitut est Nette\Utils\Arrays::invoke:
Nette\Utils\Arrays::invoke($this->onChange, $this, $radius);