SmartObject
O SmartObject costumava corrigir o comportamento dos objetos de várias maneiras, mas o PHP atual já inclui a maioria desses aprimoramentos nativamente. No entanto, ele ainda adiciona suporte para property.
Instalação:
composer require nette/utils
Propriedades, Getters e Setters
Em linguagens modernas orientadas a objetos (por exemplo, C#, Python, Ruby, JavaScript), o termo propriedade se refere a membros especiais de classes que se parecem com variáveis, mas que na verdade são representadas por métodos. Quando o valor desta “variável” é atribuído ou lido, o método correspondente (chamado getter ou setter) é chamado. Isto é uma coisa muito útil de se fazer, nos dá total controle sobre o acesso às variáveis. Podemos validar a entrada ou gerar resultados somente quando a propriedade é lida.
As propriedades do PHP não são suportadas, mas o traço Nette\SmartObject
pode imitá-las. Como
utilizá-lo?
- Acrescentar uma anotação à classe no formulário
@property <type> $xyz
- Crie um getter chamado
getXyz()
ouisXyz()
, um setter chamadosetXyz()
- O getter e o setter devem ser públicos ou protegidos e são opcionais, portanto pode haver uma propriedade somente de leitura ou somente de escrita.
Utilizaremos a propriedade da classe Circle para garantir que somente números não-negativos sejam colocados na variável
$radius
. Substituir public $radius
por propriedade:
/**
* @property float $radius
* @property-read bool $visible
*/
class Circle
{
use Nette\SmartObject;
private float $radius = 0.0; // não público
// getter para propriedade $radius
protected function getRadius(): float
{
return $this->radius;
}
// setter para propriedade $radius
protected function setRadius(float $radius): void
{
// valor higienizante antes de salvá-lo
$this->radius = max(0.0, $radius);
}
// adquirente por propriedade $visível
protected function isVisible(): bool
{
return $this->radius > 0;
}
}
$circle = new Circle;
$circle->radius = 10; // na verdade chama setRadius(10)
echo $circle->radius; // chama getRadius()
echo $circle->visible; // chamadas isVisible()
As propriedades são principalmente o açúcar sintático, que se destina a tornar a vida do programador mais doce, simplificando o código. Se você não os quer, não precisa usá-los.
Um vislumbre da história
O SmartObject costumava refinar o comportamento dos objetos de várias maneiras, mas o PHP atual já incorpora a maioria desses aprimoramentos de forma nativa. O texto a seguir é um olhar nostálgico para a história, lembrando-nos de como as coisas evoluíram.
Desde seu início, o modelo de objeto do PHP sofria de uma infinidade de falhas e deficiências graves. Isso levou à
criação da classe Nette\Object
(em 2007), cujo objetivo era corrigir esses problemas e aumentar o conforto do uso
do PHP. Tudo o que era necessário era que outras classes herdassem essa classe e obtivessem os benefícios que ela oferecia.
Quando o PHP 5.4 introduziu o suporte a características, a classe Nette\Object
foi substituída pela
característica Nette\SmartObject
. Isso eliminou a necessidade de herdar de um ancestral comum. Além disso, a
característica poderia ser usada em classes que já herdavam de outra classe. O fim definitivo de Nette\Object
veio
com o lançamento do PHP 7.2, que proibiu que as classes fossem nomeadas Object
.
À medida que o desenvolvimento do PHP continuava, seu modelo de objeto e os recursos da linguagem foram aprimorados. Várias
funções da classe SmartObject
tornaram-se redundantes. Desde o lançamento do PHP 8.2, resta apenas um recurso
não suportado diretamente no PHP: a capacidade de usar as chamadas propriedades.
Quais recursos o Nette\Object
e, por extensão, o Nette\SmartObject
ofereceram? Aqui está uma visão
geral. (Nos exemplos, a classe Nette\Object
é usada, mas a maioria dos recursos também se aplica à característica
Nette\SmartObject
).
Erros Inconsistentes
O PHP tinha um comportamento inconsistente ao acessar membros não declarados. O estado, na época de
Nette\Object
, era o seguinte:
echo $obj->undeclared; // E_NOTICE, mais tarde E_WARNING
$obj->undeclared = 1; // passa silenciosamente sem informar
$obj->unknownMethod(); // Erro fatal (não detectável por tentativa/captura)
O erro fatal terminou a aplicação sem qualquer possibilidade de reação. Escrever silenciosamente a membros inexistentes
sem aviso prévio poderia levar a erros graves que eram difíceis de detectar. Nette\Object
Todos estes casos foram
pegos e uma exceção MemberAccessException
foi lançada.
echo $obj->undeclared; // jogue Nette\MemberAccessException
$obj->undeclared = 1; // jogar Nette\MemberAccessException
$obj->unknownMethod(); // jogar Nette\MemberAccessException
Desde o PHP 7.0, o PHP não causa mais erros fatais não detectáveis, e acessar membros não declarados tem sido um erro desde o PHP 8.2.
Você quis dizer?
Se um erro Nette\MemberAccessException
foi lançado, talvez devido a um erro de digitação ao acessar uma
variável de objeto ou ao chamar um método, Nette\Object
tentou dar uma dica na mensagem de erro sobre como corrigir
o erro, na forma do icônico adendo “você quis dizer…”.
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()?"
Embora o PHP atual não tenha um recurso “você quis dizer?”, essa frase pode ser adicionada aos erros pela Tracy. Ele pode até mesmo corrigir automaticamente esses erros.
Métodos de extensão
Inspirado pelos métodos de extensão da C#. Eles deram a possibilidade de adicionar novos métodos às classes existentes. Por
exemplo, você poderia adicionar o método addDateTime()
a um formulário para adicionar seu próprio
DateTimePicker.
Form::extensionMethod(
'addDateTime',
fn(Form $form, string $name) => $form[$name] = new DateTimePicker,
);
$form = new Form;
$form->addDateTime('date');
Os métodos de extensão se mostraram impraticáveis porque seus nomes não foram autocompletados pelos editores, em vez disso, eles relataram que o método não existia. Portanto, seu apoio foi descontinuado.
Como obter o nome da classe
$class = $obj->getClass(); // usando Nette\Object
$class = $obj::class; // desde PHP 8.0
Acesso à Reflexão e Anotações
Nette\Object
ofereceu acesso à reflexão e à anotação utilizando os métodos getReflection()
e
getAnnotation()
:
/**
* @author John Doe
*/
class Foo extends Nette\Object
{
}
$obj = new Foo;
$reflection = $obj->getReflection();
$reflection->getAnnotation('author'); // devolve 'John Doe'
A partir do PHP 8.0, é possível acessar meta-informação sob a 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
oferecia uma maneira elegante de lidar com métodos como se fossem variáveis:
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 do PHP 8.1, você pode usar a chamada sintaxe de primeira classe que pode ser chamada:
$obj = new Foo;
$method = $obj->adder(...);
echo $method(2, 3); // 5
Eventos
Nette\Object
ofereceu açúcar sintáctico para acionar o evento:
class Circle extends Nette\Object
{
public array $onChange = [];
public function setRadius(float $radius): void
{
$this->onChange($this, $radius);
$this->radius = $radius;
}
}
O código $this->onChange($this, $radius)
é equivalente ao seguinte:
foreach ($this->onChange as $callback) {
$callback($this, $radius);
}
Por uma questão de clareza, recomendamos evitar o método mágico $this->onChange()
. Um substituto prático
é a Arrays Nette\Utils::invoke function:
Nette\Utils\Arrays::invoke($this->onChange, $this, $radius);