SmartObject und StaticClass
SmartObject fügt PHP-Klassen Unterstützung für Eigenschaften hinzu. StaticClass wird verwendet, um statische Klassen zu bezeichnen.
Installation:
composer require nette/utils
Eigenschaften, Getters und Setters
In modernen objektorientierten Sprachen (z. B. C#, Python, Ruby, JavaScript) bezieht sich der Begriff Eigenschaft auf spezielle Mitglieder von Klassen, die wie Variablen aussehen, aber eigentlich durch Methoden repräsentiert werden. Wenn der Wert dieser „Variablen“ zugewiesen oder gelesen wird, wird die entsprechende Methode (Getter oder Setter genannt) aufgerufen. Das ist sehr praktisch, denn es gibt uns die volle Kontrolle über den Zugriff auf Variablen. Wir können die Eingabe validieren oder Ergebnisse nur dann erzeugen, wenn die Eigenschaft gelesen wird.
PHP-Eigenschaften werden nicht unterstützt, aber Trait Nette\SmartObject
kann sie imitieren. Wie verwendet
man es?
- Fügen Sie der Klasse eine Annotation in der Form
@property <type> $xyz
- Erstellen Sie einen Getter namens
getXyz()
oderisXyz()
, einen Setter namenssetXyz()
- Die Getter und Setter müssen public oder protected sein und sind optional, d.h. es kann eine read-only oder write-only Eigenschaft geben
Wir werden die Eigenschaft für die Klasse Circle verwenden, um sicherzustellen, dass nur nicht-negative Zahlen in die Variable
$radius
eingegeben werden. Ersetzen Sie public $radius
durch property:
/**
* @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()
Eigenschaften sind in erster Linie „syntaktischer Zucker“ ((syntactic sugar)), der das Leben des Programmierers durch Vereinfachung des Codes versüßen soll. Wenn Sie sie nicht wollen, müssen Sie sie nicht verwenden.
Statische Klassen
Statische Klassen, d.h. Klassen, die nicht instanziiert werden sollen, können mit dem Trait Nette\StaticClass
gekennzeichnet werden:
class Strings
{
use Nette\StaticClass;
}
Wenn Sie versuchen, eine Instanz zu erstellen, wird die Ausnahme Error
ausgelöst, die angibt, dass die Klasse
statisch ist.
Ein Blick in die Geschichte
SmartObject hat das Verhalten von Klassen in vielerlei Hinsicht verbessert und korrigiert, aber die Entwicklung von PHP hat die meisten der ursprünglichen Funktionen überflüssig gemacht. Im Folgenden werfen wir einen Blick in die Geschichte, wie sich die Dinge entwickelt haben.
Von Anfang an litt das PHP-Objektmodell unter einer Reihe von schwerwiegenden Mängeln und Ineffizienzen. Dies war der Grund
für die Schaffung der Klasse Nette\Object
(im Jahr 2007), mit der versucht wurde, diese zu beheben und die Benutzung
von PHP zu verbessern. Es reichte aus, dass andere Klassen von ihr erbten und von den Vorteilen profitierten, die sie mit sich
brachte. Als PHP 5.4 mit Trait-Unterstützung kam, wurde die Klasse Nette\Object
durch
Nette\SmartObject
ersetzt. Somit war es nicht mehr notwendig, von einem gemeinsamen Vorfahren zu erben. Darüber
hinaus konnte Trait in Klassen verwendet werden, die bereits von einer anderen Klasse geerbt hatten. Das endgültige Ende von
Nette\Object
kam mit der Veröffentlichung von PHP 7.2, die es verbot, Klassen den Namen Object
zu geben.
Im weiteren Verlauf der PHP-Entwicklung wurden das Objektmodell und die Sprachfähigkeiten verbessert. Die einzelnen Funktionen
der Klasse SmartObject
wurden überflüssig. Seit der Veröffentlichung von PHP 8.2 ist die einzige Funktion, die
noch nicht direkt in PHP unterstützt wird, die Möglichkeit, sogenannte Properties zu verwenden.
Welche Funktionen boten Nette\Object
und Nette\Object
früher? Hier ist ein Überblick. (Die
Beispiele verwenden die Klasse Nette\Object
, aber die meisten der Eigenschaften gelten auch für die Eigenschaft
Nette\SmartObject
).
Inkonsistente Fehler
PHP hatte ein inkonsistentes Verhalten beim Zugriff auf nicht deklarierte Mitglieder. Der Zustand zum Zeitpunkt von
Nette\Object
war wie folgt:
echo $obj->undeclared; // E_NOTICE, later E_WARNING
$obj->undeclared = 1; // passes silently without reporting
$obj->unknownMethod(); // Fatal error (not catchable by try/catch)
Ein schwerwiegender Fehler beendete die Anwendung, ohne dass eine Möglichkeit zur Reaktion bestand. Das stille Schreiben auf
nicht existierende Mitglieder ohne Warnung konnte zu schwerwiegenden Fehlern führen, die schwer zu erkennen waren.
Nette\Object
Alle diese Fälle wurden abgefangen und eine Ausnahme MemberAccessException
wurde
ausgelöst.
echo $obj->undeclared; // throw Nette\MemberAccessException
$obj->undeclared = 1; // throw Nette\MemberAccessException
$obj->unknownMethod(); // throw Nette\MemberAccessException
Seit PHP 7.0 verursacht PHP keine nicht abfangbaren fatalen Fehler mehr, und der Zugriff auf nicht deklarierte Member ist seit PHP 8.2 ein Fehler.
Haben Sie gemeint?
Wenn ein Nette\MemberAccessException
-Fehler ausgelöst wurde, etwa aufgrund eines Tippfehlers beim Zugriff auf
eine Objektvariable oder beim Aufruf einer Methode, versuchte Nette\Object
, in der Fehlermeldung einen Hinweis darauf
zu geben, wie der Fehler zu beheben ist, und zwar in Form des ikonischen Zusatzes „Meinten Sie?“.
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()?"
Das heutige PHP hat zwar keine Form von „did you mean?“, aber Tracy fügt diesen Zusatz zu Fehlern hinzu. Und es kann solche Fehler sogar selbst beheben.
Erweiterungsmethoden
Inspiriert von Erweiterungsmethoden aus C#. Sie bieten die Möglichkeit, neue Methoden zu bestehenden Klassen hinzuzufügen.
Zum Beispiel könnten Sie die Methode addDateTime()
zu einem Formular hinzufügen, um Ihren eigenen DateTimePicker
hinzuzufügen.
Form::extensionMethod(
'addDateTime',
fn(Form $form, string $name) => $form[$name] = new DateTimePicker,
);
$form = new Form;
$form->addDateTime('date');
Erweiterungsmethoden erwiesen sich als unpraktisch, da ihre Namen von Editoren nicht automatisch vervollständigt wurden, sondern sie meldeten, dass die Methode nicht existierte. Daher wurde ihre Unterstützung eingestellt.
Ermitteln des Klassennamens
$class = $obj->getClass(); // using Nette\Object
$class = $obj::class; // since PHP 8.0
Zugang zu Reflexion und Anmerkungen
Nette\Object
bietet den Zugang zu Reflexion und Kommentaren mit den Methoden getReflection()
und
getAnnotation()
:
/**
* @author John Doe
*/
class Foo extends Nette\Object
{
}
$obj = new Foo;
$reflection = $obj->getReflection();
$reflection->getAnnotation('author'); // returns 'John Doe
Seit PHP 8.0 ist es möglich, auf Metainformationen in Form von Attributen zuzugreifen:
#[Author('John Doe')]
class Foo
{
}
$obj = new Foo;
$reflection = new ReflectionObject($obj);
$reflection->getAttributes(Author::class)[0];
Methoden-Getter
Nette\Object
boten eine elegante Möglichkeit, mit Methoden so umzugehen, als wären sie Variablen:
class Foo extends Nette\Object
{
public function adder($a, $b)
{
return $a + $b;
}
}
$obj = new Foo;
$method = $obj->adder;
echo $method(2, 3); // 5
Seit PHP 8.1 können Sie die sogenannte First-Class-Callable-Syntax verwenden:
$obj = new Foo;
$method = $obj->adder(...);
echo $method(2, 3); // 5
Ereignisse
Nette\Object
bietet syntaktischen Zucker zum Auslösen des Ereignisses:
class Circle extends Nette\Object
{
public array $onChange = [];
public function setRadius(float $radius): void
{
$this->onChange($this, $radius);
$this->radius = $radius
}
}
Der Code $this->onChange($this, $radius)
entspricht folgendem:
foreach ($this->onChange as $callback) {
$callback($this, $radius);
}
Aus Gründen der Übersichtlichkeit wird empfohlen, die magische Methode $this->onChange()
zu vermeiden. Ein
guter Ersatz ist Nette\Utils\Arrays::invoke:
Nette\Utils\Arrays::invoke($this->onChange, $this, $radius);