SmartObject: Mejoras en los objetos PHP
SmartObject extiende las capacidades de objetos de PHP con algunos dulces sintácticos. ¿Le gustan los dulces? Lea y aprenda
- por qué es bueno usar
Nette\SmartObject
- qué son las *propiedades
- cómo activar eventos
En este capítulo, nos centraremos en Nette\SmartObject
, un rasgo que extiende las capacidades de objetos de PHP.
Este trait es usado por casi todas las clases en el Nette Framework. Al mismo tiempo, es lo suficientemente transparente como para
ser usado en tus clases. Intentemos decir por qué deberías hacerlo.
Trait Nette\SmartObject
es un sucesor simplificado de la clase obsoleta Nette\Object
de
Nette 2.x.
Instalación:
composer require nette/utils
Clases estrictas
PHP da una enorme libertad a los desarrolladores, lo que lo convierte en un lenguaje perfecto para cometer errores. Pero usted puede detener este mal comportamiento y comenzar a escribir aplicaciones sin errores difícilmente descubribles. ¿Se pregunta cómo? Es realmente simple – sólo necesitas tener reglas más estrictas.
¿Puedes encontrar un error en este ejemplo?
class Circle
{
public $radius = 0.0;
public function getArea(): float
{
return $this->radius * $this->radius * M_PI;
}
}
$circle = new Circle;
$circle->raduis = 10;
echo $circle->getArea(); // 10² * π ≈ 314
A primera vista, parece que el código imprimirá 314; pero devuelve 0. ¿Cómo es posible? Accidentalmente,
$circle->radius
fue mal escrito a raduis
. Sólo un pequeño error tipográfico, que le costará
corregir porque PHP no dice nada cuando algo está mal. Ni siquiera un mensaje de error de Advertencia o Aviso. Porque PHP no
cree que sea un error.
El error mencionado podría ser corregido inmediatamente, si la clase Circle
usara
Nette\SmartObject
:
class Circle
{
use Nette\SmartObject;
}
Mientras que el primer código se ejecutó correctamente (aunque contenía un error), el segundo no:

Trait Nette\SmartObject
hacía Circle
más estricto y lanzaba una excepción cuando se intentaba
acceder a una propiedad no declarada. Y Tracy mostraba un mensaje de error al respecto.
La línea de código con una errata fatal está ahora resaltada y el mensaje de error tiene una descripción significativa:
Cannot write to an undeclared property Circle::$raduis, ¿querías decir $radius?. El programador puede ahora corregir el
error que de otro modo podría haber pasado por alto y que podría ser un verdadero dolor de cabeza encontrar más tarde.
Una de las muchas capacidades notables de Nette\SmartObject
es lanzar excepciones cuando se accede a miembros no
declarados.
$circle = new Circle;
echo $circle->undeclared; // throws Nette\MemberAccessException
$circle->undeclared = 1; // throws Nette\MemberAccessException
$circle->unknownMethod(); // throws Nette\MemberAccessException
Pero tiene mucho más que ofrecer.
Utilice SmartObject
sólo para las clases base que no heredan de nadie, la funcionalidad se
transmitirá en todos sus descendientes.
¿Ha querido decir?
Si cometes un error tipográfico al acceder a una variable de objeto o al llamar a un método, se lanza una excepción que intenta indicarte dónde está el error. Contiene el icónico apéndice “¿querías decir?”.
class Foo
{
use Nette\SmartObject;
public static function from($var)
{
// ...
}
}
$foo = Foo::form($var);
// it throws Nette\MemberAccessException
// "Call to undefined static method Foo::form(), did you mean from()?"
Algunas erratas pueden ser literalmente invisibles. El cerebro está acostumbrado a ver tanto form
como
from
, y puede ocurrir fácilmente que usted mire el nombre del método y simplemente no vea el error, incluso si no
tiene dislexia. Pero si lee Foo::form(), did you mean from()?
en el texto de la excepción, se dará cuenta
inmediatamente de la errata.
SmartObject incluye en las sugerencias no sólo todos los métodos y propiedades de la clase, sino también los miembros
mágicos/virtuales definidos por las anotaciones @method
y @property
. Y lo mejor de todo es que Tracy
puede corregir estos errores automáticamente.
Propiedades, Getters y Setters
(Para programadores más experimentados)
En los lenguajes modernos orientados a objetos (p.e. C#, Python, Ruby, JavaScript), el término propiedad se refiere a miembros especiales de una clase, que parecen variables pero están representados por métodos. Cuando se leen o asignan valores a esas “variables”, en su lugar se llama a métodos (los llamados getters y setters). Es una característica realmente útil, que nos permite controlar el acceso a estas variables. Usando esto podemos validar entradas o posponer el cálculo de valores de estas variables al momento en que realmente se accede a ellas.
Cualquier clase que utilice Nette\SmartObject
adquiere la capacidad de imitar propiedades. ¿Cómo hacerlo?
- Añade una anotación a la clase de la forma
@property <type> $xyz
- Crea un getter llamado
getXyz()
oisXyz()
, un setter llamadosetXyz()
- Getter y setter deben ser public o protected y ambos son opcionales, por lo que puede haber propiedades read-only o *write-only
Haremos uso de las propiedades de la clase Circle para asegurarnos de que la variable $radius
sólo contiene
números no negativos. Sustituiremos public $ radius
por la propiedad
/**
* @property float $radius
* @property-read bool $visible
*/
class Circle
{
use Nette\SmartObject;
private float $radius = 0.0; // ¡ya no es público!
// 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; // calls setRadius(10)
echo $circle->radius; // calls getRadius()
echo $circle->visible; // calls isVisible()
Las propiedades son principalmente un azúcar sintáctico para embellecer el código y hacer la vida del programador más fácil. Usted no tiene que usarlos, si no quieres.
Eventos
(Para programadores más experimentados)
Ahora vamos a crear funciones, que serán llamadas cuando radius cambie. Vamos a llamarlo change
evento y esas
funciones manejadores de eventos:
class Circle
{
use Nette\SmartObject;
public array $onChange = [];
public function setRadius(float $radius): void
{
// it calls callbacks in $onChange with parameters $this, $radius
$this->onChange($this, $radius);
// better: Nette\Utils\Arrays::invoke($this->onChange, $this, $radius);
$this->radius = max(0.0, $radius);
}
}
$circle = new Circle;
// adding an event handler
$circle->onChange[] = function (Circle $circle, float $newValue): void {
echo 'there was a change!';
};
$circle->setRadius(10);
En el código del método setRadius
, ves azúcar sintáctico – en lugar de iterar sobre el array
$onChange
y llamar a cada callback, sólo tienes que escribir un simple onChange(...)
y especificar los
parámetros que se pasan a cada callback. Así, SmartObject crea un método ficticio onChange()
con el nombre del
array $onChange
. Se requiere una convención de on
+ palabra.
En aras de la claridad del código, le recomendamos que evite este azúcar sintáctico y utilice la función Nette\Utils\Arrays::invoke para llamar a los callbacks.
Clases estáticas
Puede marcar las clases estáticas, es decir, las clases que no están destinadas a ser instanciadas, con
Nette\StaticClass
trait:
class Strings
{
use Nette\StaticClass;
}
Al intentar instanciarla, se lanza una excepción Error
con la información de que la clase especificada es
static.