SmartObject
SmartObject solía arreglar el comportamiento de los objetos de muchas maneras, pero el PHP actual ya incluye la mayoría de estas mejoras de forma nativa. Sin embargo, aún añade soporte para property.
Instalación:
composer require nette/utils
Propiedades, getters y setters
En los lenguajes modernos orientados a objetos (por ejemplo, C#, Python, Ruby, JavaScript), el término propiedad se refiere a miembros especiales de las clases que parecen variables pero que en realidad están representados por métodos. Cuando se asigna o se lee el valor de esta „variable“, se llama al método correspondiente (llamado getter o setter). Esto es muy práctico, nos da un control total sobre el acceso a las variables. Podemos validar la entrada o generar resultados sólo cuando la propiedad es leída.
Las propiedades PHP no están soportadas, pero trait Nette\SmartObject
puede imitarlas. ¿Cómo usarlo?
- Añade una anotación a la clase de la forma
@property <type> $xyz
- Crea un getter llamado
getXyz()
oisXyz()
, un setter llamadosetXyz()
- El getter y el setter deben ser public o protected y son opcionales, por lo que puede haber una propiedad read-only o *write-only
Utilizaremos la propiedad de la clase Circle para asegurarnos de que sólo se introducen números no negativos en la variable
$radius
. Sustituye public $radius
por propiedad:
/**
* @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()
Las propiedades son principalmente azúcar sintáctico, que pretende hacer la vida del programador más dulce simplificando el código. Si no las quieres, no tienes por qué usarlas.
Un vistazo a la Historia
SmartObject solía refinar el comportamiento de los objetos de numerosas maneras, pero el PHP actual ya incorpora la mayoría de estas mejoras de forma nativa. El siguiente texto es una mirada nostálgica a la historia, recordándonos como evolucionaron las cosas.
Desde sus inicios, el modelo de objetos de PHP sufrió de una miríada de serios defectos y deficiencias. Esto llevó a la
creación de la clase Nette\Object
(en 2007), que pretendía rectificar estos problemas y mejorar la comodidad de uso
de PHP. Todo lo que se necesitaba era que otras clases heredaran de ella, y obtendrían los beneficios que ofrecía. Cuando PHP
5.4 introdujo el soporte para traits, la clase Nette\Object
fue reemplazada por el trait
Nette\SmartObject
. Esto eliminó la necesidad de heredar de un ancestro común. Además, el trait podía ser usado en
clases que ya heredaban de otra clase. El fin definitivo de Nette\Object
llegó con el lanzamiento de PHP 7.2, que
prohibió que las clases se llamaran Object
.
A medida que el desarrollo de PHP continuaba, su modelo de objetos y las capacidades del lenguaje mejoraron. Varias funciones
de la clase SmartObject
se volvieron redundantes. Desde el lanzamiento de PHP 8.2, sólo queda una característica no
soportada directamente en PHP: la capacidad de usar las llamadas propiedades.
¿Qué características ofrecían Nette\Object
y, por extensión, Nette\SmartObject
? He aquí un
resumen. (En los ejemplos, se usa la clase Nette\Object
, pero la mayoría de las características también se aplican
al rasgo Nette\SmartObject
).
Errores incoherentes
PHP tenía un comportamiento inconsistente al acceder a miembros no declarados. El estado en el momento de
Nette\Object
era el siguiente:
echo $obj->undeclared; // E_NOTICE, later E_WARNING
$obj->undeclared = 1; // passes silently without reporting
$obj->unknownMethod(); // Fatal error (not catchable by try/catch)
Un error fatal terminaba la aplicación sin posibilidad de reaccionar. La escritura silenciosa en miembros inexistentes sin
previo aviso podía dar lugar a errores graves difíciles de detectar. Nette\Object
Todos estos casos eran detectados
y se lanzaba una excepción MemberAccessException
.
echo $obj->undeclared; // throw Nette\MemberAccessException
$obj->undeclared = 1; // throw Nette\MemberAccessException
$obj->unknownMethod(); // throw Nette\MemberAccessException
Desde PHP 7.0, PHP ya no causa errores fatales no capturables, y acceder a miembros no declarados ha sido un error desde PHP 8.2.
¿Te refieres a?
Si se lanzaba un error Nette\MemberAccessException
, quizás debido a un error tipográfico al acceder a una
variable de objeto o al llamar a un método, Nette\Object
intentaba dar una pista en el mensaje de error sobre cómo
solucionar el error, en la forma del icónico apéndice „¿querías decir?“.
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()?"
Aunque el PHP actual no tiene la función „¿Querías decir?“, esta frase puede ser añadida a los errores por Tracy. Incluso puede autocorregir dichos errores.
Métodos de extensión
Inspirado en los métodos de extensión de C#. Ofrecen la posibilidad de añadir nuevos métodos a clases existentes. Por
ejemplo, podrías añadir el método addDateTime()
a un formulario para añadir tu propio DateTimePicker.
Form::extensionMethod(
'addDateTime',
fn(Form $form, string $name) => $form[$name] = new DateTimePicker,
);
$form = new Form;
$form->addDateTime('date');
Los métodos de extensión resultaron ser poco prácticos porque sus nombres no eran autocompletados por los editores, sino que informaban de que el método no existía. Por lo tanto, su soporte fue descontinuado.
Obtención del nombre de la clase
$class = $obj->getClass(); // using Nette\Object
$class = $obj::class; // since PHP 8.0
Acceso a la reflexión y a las anotaciones
Nette\Object
ofrece acceso a la reflexión y a las anotaciones mediante los métodos getReflection()
y getAnnotation()
:
/**
* @author John Doe
*/
class Foo extends Nette\Object
{
}
$obj = new Foo;
$reflection = $obj->getReflection();
$reflection->getAnnotation('author'); // returns 'John Doe'
A partir de PHP 8.0, es posible acceder a meta-información en forma de atributos:
#[Author('John Doe')]
class Foo
{
}
$obj = new Foo;
$reflection = new ReflectionObject($obj);
$reflection->getAttributes(Author::class)[0];
Método getters
Nette\Object
ofrecían una forma elegante de tratar los métodos como si fueran variables:
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 partir de PHP 8.1, se puede utilizar la llamada sintaxis callable de primera clase:
$obj = new Foo;
$method = $obj->adder(...);
echo $method(2, 3); // 5
Eventos
Nette\Object
ofrece azúcar sintáctico para activar el evento:
class Circle extends Nette\Object
{
public array $onChange = [];
public function setRadius(float $radius): void
{
$this->onChange($this, $radius);
$this->radius = $radius;
}
}
El código $this->onChange($this, $radius)
es equivalente al siguiente:
foreach ($this->onChange as $callback) {
$callback($this, $radius);
}
Por claridad, recomendamos evitar el método mágico $this->onChange()
. Un buen sustituto es Nette\Utils\Arrays::invoke:
Nette\Utils\Arrays::invoke($this->onChange, $this, $radius);