SmartObject
SmartObject correggeva il comportamento degli oggetti in molti modi, ma il PHP di oggi include già la maggior parte di questi miglioramenti in modo nativo. Tuttavia, aggiunge ancora il supporto per le proprietà.
Installazione:
composer require nette/utils
Proprietà, Getter e Setter
Nei moderni linguaggi orientati agli oggetti (ad esempio, C#, Python, Ruby, JavaScript), il termine property si riferisce a membri speciali delle classi che sembrano variabili, ma in realtà sono rappresentati da metodi. Quando il valore di questa “variabile” viene assegnato o letto, viene richiamato il metodo corrispondente (chiamato getter o setter). Si tratta di una cosa molto comoda, che ci dà il pieno controllo sull'accesso alle variabili. Possiamo convalidare l'input o generare risultati solo quando la proprietà viene letta.
Le proprietà PHP non sono supportate, ma il trait Nette\SmartObject
può imitarle. Come si usa?
- Aggiungere un'annotazione alla classe nella forma
@property <type> $xyz
- Creare un getter chiamato
getXyz()
oisXyz()
, un setter chiamatosetXyz()
- Il getter e il setter devono essere pubblici o protetti e sono opzionali, quindi può esserci una proprietà di sola lettura o di sola scrittura.
Utilizzeremo la proprietà per la classe Circle per garantire che nella variabile $radius
vengano inseriti solo
numeri non negativi. Sostituire public $radius
con la proprietà:
/**
* @property float $radius
* @property-read bool $visible
*/
class Circle
{
use Nette\SmartObject;
private float $radius = 0.0; // non pubblico
// getter per la proprietà $radius
protected function getRadius(): float
{
return $this->radius;
}
// setter per la proprietà $radius
protected function setRadius(float $radius): void
{
// sanitizza il valore prima di salvarlo
$this->radius = max(0.0, $radius);
}
// getter per la proprietà $visible
protected function isVisible(): bool
{
return $this->radius > 0;
}
}
$circle = new Circle;
$circle->radius = 10; // in realtà chiama setRadius(10)
echo $circle->radius; // chiama getRadius()
echo $circle->visible; // chiama isVisible()
Le proprietà sono principalmente “zucchero sintattico” ((syntactic sugar)), che ha lo scopo di rendere più dolce la vita del programmatore semplificando il codice. Se non le si vuole, non è necessario usarle.
Uno sguardo alla storia
SmartObject era solito perfezionare il comportamento degli oggetti in molti modi, ma il PHP di oggi incorpora già la maggior parte di questi miglioramenti in modo nativo. Il testo che segue è uno sguardo nostalgico alla storia, che ci ricorda come si sono evolute le cose.
Fin dall'inizio, il modello a oggetti di PHP soffriva di una miriade di gravi mancanze e carenze. Questo ha portato alla
creazione della classe Nette\Object
(nel 2007), che mirava a correggere questi problemi e a migliorare il comfort
dell'utilizzo di PHP. Bastava che altre classi ereditassero da essa per ottenere i vantaggi che offriva. Quando PHP 5.4 ha
introdotto il supporto ai tratti, la classe Nette\Object
è stata sostituita dal tratto
Nette\SmartObject
. Questo ha eliminato la necessità di ereditare da un antenato comune. Inoltre, il tratto poteva
essere usato in classi che già ereditavano da un'altra classe. La fine definitiva di Nette\Object
avvenne con il
rilascio di PHP 7.2, che proibiva alle classi di chiamarsi Object
.
Con il proseguire dello sviluppo di PHP, il suo modello di oggetti e le capacità del linguaggio sono migliorati. Diverse
funzioni della classe SmartObject
sono diventate superflue. Dal rilascio di PHP 8.2, rimane solo una caratteristica
non supportata direttamente da PHP: la possibilità di usare le cosiddette proprietà.
Quali caratteristiche offrivano Nette\Object
e, per estensione, Nette\SmartObject
? Ecco una
panoramica. (Negli esempi viene utilizzata la classe Nette\Object
, ma la maggior parte delle caratteristiche si
applicano anche al tratto Nette\SmartObject
).
Errori inconsistenti
PHP aveva un comportamento incoerente quando si accedeva a membri non dichiarati. Lo stato al momento di
Nette\Object
era il seguente:
echo $obj->undeclared; // E_NOTICE, successivamente E_WARNING
$obj->undeclared = 1; // passa in modo silenzioso, senza segnalare nulla
$obj->unknownMethod(); // Errore fatale (non catturabile con try/catch)
L'errore fatale terminava l'applicazione senza alcuna possibilità di reagire. Scrivere silenziosamente su membri inesistenti
senza preavviso poteva portare a errori gravi, difficili da individuare. Nette\Object
Tutti questi casi sono stati
catturati ed è stata lanciata l'eccezione MemberAccessException
.
echo $obj->undeclared; // lancia l'eccezione Nette\MemberAccessException
$obj->undeclared = 1; // lanciare l'eccezione Nette\MemberAccessException
$obj->unknownMethod(); // lanciare l'eccezione Nette\MemberAccessException
A partire da PHP 7.0, PHP non causa più errori fatali non catturabili e l'accesso a membri non dichiarati è un bug da PHP 8.2.
Intendevi dire?
Se veniva lanciato un errore Nette\MemberAccessException
, magari a causa di un errore di battitura nell'accesso a
una variabile oggetto o nella chiamata di un metodo, Nette\Object
cercava di dare un suggerimento nel messaggio di
errore su come correggere l'errore, sotto forma dell'iconico addendum "intendevi?
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()?"
Sebbene il PHP di oggi non disponga di una funzione di “hai inteso?”, questa frase può essere aggiunta agli errori da Tracy. Può anche correggere automaticamente tali errori.
Metodi di estensione
Ispirato ai metodi di estensione di C#. Offrono la possibilità di aggiungere nuovi metodi a classi esistenti. Ad esempio, si
può aggiungere il metodo addDateTime()
a un modulo per aggiungere il proprio DateTimePicker.
Form::extensionMethod(
'addDateTime',
fn(Form $form, string $name) => $form[$name] = new DateTimePicker,
);
$form = new Form;
$form->addDateTime('date');
I metodi di estensione si sono rivelati poco pratici, perché i loro nomi non venivano autocompilati dagli editor, che invece segnalavano che il metodo non esisteva. Pertanto, il loro supporto è stato interrotto.
Ottenere il nome della classe
$class = $obj->getClass(); // usando Nette\Object
$class = $obj::class; // da PHP 8.0
Accesso alla riflessione e alle annotazioni
Nette\Object
ha offerto l'accesso alla riflessione e alle annotazioni utilizzando i metodi
getReflection()
e getAnnotation()
:
/**
* @author John Doe
*/
class Foo extends Nette\Object
{
}
$obj = new Foo;
$reflection = $obj->getReflection();
$reflection->getAnnotation('author'); // restituisce 'John Doe'.
A partire da PHP 8.0, è possibile accedere alle meta-informazioni sotto forma di attributi:
#[Author('John Doe')]
class Foo
{
}
$obj = new Foo;
$reflection = new ReflectionObject($obj);
$reflection->getAttributes(Author::class)[0];
Metodi Getter
Nette\Object
offre un modo elegante per trattare i metodi come se fossero variabili:
class Foo extends Nette\Object
{
public function adder($a, $b)
{
return $a + $b;
}
}
$obj = new Foo;
$method = $obj->adder;
echo $method(2, 3); // 5
A partire da PHP 8.1, è possibile utilizzare la cosiddetta sintassi callable di prima classe:
$obj = new Foo;
$method = $obj->adder(...);
echo $method(2, 3); // 5
Eventi
Nette\Object
ha offerto uno zucchero sintattico per innescare l'evento:
class Circle extends Nette\Object
{
public array $onChange = [];
public function setRadius(float $radius): void
{
$this->onChange($this, $radius);
$this->radius = $radius;
}
}
Il codice $this->onChange($this, $radius)
è equivalente al seguente:
foreach ($this->onChange as $callback) {
$callback($this, $radius);
}
Per motivi di chiarezza si consiglia di evitare il metodo magico $this->onChange()
. Un sostituto pratico è la
funzione Nette\Utils\Arrays::invoke:
Nette\Utils\Arrays::invoke($this->onChange, $this, $radius);