SmartObject e StaticClass
SmartObject aggiunge il supporto per le proprietà alle classi PHP. StaticClass è usato per indicare le classi statiche.
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.
Classi statiche
Le classi statiche, cioè quelle che non sono destinate ad essere istanziate, possono essere contrassegnate con il tratto
Nette\StaticClass
:
class Strings
{
use Nette\StaticClass;
}
Quando si tenta di creare un'istanza, viene lanciata l'eccezione Error
, che indica che la classe è statica.
Uno sguardo alla storia
SmartObject migliorava e correggeva il comportamento delle classi in molti modi, ma l'evoluzione di PHP ha reso superflua la maggior parte delle caratteristiche originali. Di seguito viene presentata la storia di come si sono evolute le cose.
Fin dall'inizio, il modello a oggetti di PHP soffriva di una serie di gravi difetti e inefficienze. Questo è stato il motivo
della creazione della classe Nette\Object
(nel 2007), che ha cercato di porvi rimedio e di migliorare l'esperienza
d'uso di PHP. È bastato che altre classi ereditassero da essa per trarne i benefici. Quando PHP 5.4 ha introdotto il supporto
ai trait, la classe Nette\Object
è stata sostituita da Nette\SmartObject
. Pertanto, non era più
necessario ereditare da un antenato comune. Inoltre, i trait potevano essere usati 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 modello a oggetti e le funzionalità del linguaggio sono stati migliorati. Le
singole funzioni della classe SmartObject
sono diventate superflue. Dal rilascio di PHP 8.2, l'unica caratteristica
rimasta che non è ancora direttamente supportata in PHP è la possibilità di usare le cosiddette proprietà.
Quali funzioni offrivano un tempo Nette\Object
e Nette\Object
? Ecco una panoramica. (Gli esempi
utilizzano la classe Nette\Object
, ma la maggior parte delle proprietà 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()?"
Il PHP di oggi potrebbe non avere alcuna forma di “did you mean?”, ma Tracy aggiunge questa appendice agli errori. E può persino correggere tali errori da solo.
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);