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() ou isXyz(), 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);
version: 4.0