SmartObject et StaticClass

SmartObject ajoute le support des propriétés aux classes PHP. StaticClass est utilisé pour désigner les classes statiques.

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.

Classes statiques

Les classes statiques, c'est-à-dire les classes qui ne sont pas destinées à être instanciées, peuvent être marquées par le trait Nette\StaticClass:

class Strings
{
	use Nette\StaticClass;
}

Lorsque vous essayez de créer une instance, l'exception Error est levée, indiquant que la classe est statique.

Un regard sur l'histoire

SmartObject améliorait et corrigeait le comportement des classes de plusieurs façons, mais l'évolution de PHP a rendu la plupart des fonctionnalités originales superflues. Ce qui suit est donc un regard sur l'histoire de l'évolution des choses.

Dès le début, le modèle objet de PHP a souffert d'un certain nombre de défauts sérieux et d'inefficacités. C'est la raison de la création de la classe Nette\Object (en 2007), qui a tenté d'y remédier et d'améliorer l'expérience d'utilisation de PHP. Il a suffi que d'autres classes héritent d'elle pour bénéficier de ses avantages. Lorsque PHP 5.4 a intégré le support des traits, la classe Nette\Object a été remplacée par Nette\SmartObject. Ainsi, il n'était plus nécessaire d'hériter d'un ancêtre commun. De plus, les traits pouvaient être utilisés dans des classes qui héritaient déjà d'une autre classe. La fin définitive de Nette\Object est intervenue avec la sortie de PHP 7.2, qui interdisait aux classes d'être nommées Object.

Au fur et à mesure du développement de PHP, le modèle objet et les capacités du langage ont été améliorés. Les fonctions individuelles de la classe SmartObject sont devenues redondantes. Depuis la version 8.2 de PHP, la seule fonctionnalité qui n'est pas encore directement supportée par PHP est la possibilité d'utiliser des propriétés.

Quelles fonctionnalités offraient autrefois Nette\Object et Nette\Object? En voici un aperçu. (Les exemples utilisent la classe Nette\Object, mais la plupart des propriété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()?"

Le PHP d'aujourd'hui n'a peut-être pas de forme de “did you mean ?”, mais Tracy ajoute cet addendum aux erreurs. Et il peut même corriger ces erreurs lui-même.

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