SmartObject
SmartObject hat das Verhalten von Objekten in vielerlei Hinsicht verbessert, aber das heutige PHP enthält die meisten dieser Verbesserungen bereits von Haus aus. Es fügt jedoch noch Unterstützung für Property hinzu.
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.
Ein Blick in die Geschichte
SmartObject verfeinerte das Verhalten von Objekten auf zahlreiche Arten, aber das heutige PHP enthält die meisten dieser Erweiterungen bereits von Haus aus. Der folgende Text ist ein nostalgischer Blick zurück in die Geschichte, der uns daran erinnert, wie sich die Dinge entwickelt haben.
Seit seinen Anfängen litt das Objektmodell von PHP unter einer Vielzahl von ernsthaften Mängeln und Unzulänglichkeiten. Dies
führte zur Schaffung der Klasse Nette\Object
(im Jahr 2007), die diese Probleme beheben und den Komfort bei der
Verwendung von PHP erhöhen sollte. Andere Klassen brauchten nur von dieser Klasse zu erben, um die Vorteile zu nutzen, die sie
bot. Als mit PHP 5.4 die Unterstützung für Traits eingeführt wurde, wurde die Klasse Nette\Object
durch den Trait
Nette\SmartObject
ersetzt. Damit entfiel die Notwendigkeit, von einem gemeinsamen Vorfahren zu erben. Außerdem
konnte der 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.
Mit der weiteren Entwicklung von PHP wurden das Objektmodell und die Sprachfähigkeiten verbessert. Verschiedene Funktionen der
Klasse SmartObject
wurden überflüssig. Seit der Veröffentlichung von PHP 8.2 gibt es nur noch eine Funktion, die
in PHP nicht direkt unterstützt wird: die Möglichkeit, so genannte Properties
zu verwenden.
Welche Funktionen boten Nette\Object
und in der Folge Nette\SmartObject
? Hier ist ein Überblick. (In
den Beispielen wird die Klasse Nette\Object
verwendet, aber die meisten Funktionen gelten auch für den Trait
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 “Meinten Sie?”-Funktion, aber dieser Satz kann von Tracy zu Fehlern hinzugefügt werden. Es kann solche Fehler sogar automatisch korrigieren.
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);