SmartObject e StaticClass
O SmartObject adiciona suporte para propriedade às classes PHP. O StaticClass é usado para denotar classes estáticas.
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.
Classes estáticas
As classes estáticas, ou seja, classes que não se destinam a ser instanciadas, podem ser marcadas com o traço
Nette\StaticClass
:
class Strings
{
use Nette\StaticClass;
}
Quando você tenta criar uma instância, a exceção Error
é lançada, indicando que a classe é estática.
Um olhar sobre a história
SmartObject usado para melhorar e corrigir o comportamento de classe de muitas maneiras, mas a evolução do PHP tornou redundante a maioria das características originais. Portanto, o seguinte é um olhar sobre a história de como as coisas evoluíram.
Desde o início, o modelo de objeto PHP sofreu uma série de falhas graves e ineficiências. Esta foi a razão da criação
da classe Nette\Object
(em 2007), que tentou remediá-los e melhorar a experiência de uso do PHP. Era o suficiente
para que outras classes herdassem dela, e ganhassem os benefícios que ela trazia. Quando o PHP 5.4 veio com suporte de
características, a classe Nette\Object
foi substituída por Nette\SmartObject
. Assim, não era mais
necessário herdar de um ancestral comum. Além disso, a característica podia ser usada em classes que já herdaram de outra
classe. O fim final de Nette\Object
veio com o lançamento do PHP 7.2, que proibiu as classes de serem nomeadas
Object
.
Com o desenvolvimento do PHP em andamento, o modelo de objeto e as capacidades de linguagem foram melhorados. As funções
individuais da classe SmartObject
se tornaram redundantes. Desde o lançamento do PHP 8.2, a única característica
que ainda não é suportada diretamente no PHP é a capacidade de usar as chamadas propriedades.
Que características Nette\Object
e Nette\Object
ofereceram uma vez? Aqui está uma visão geral. (Os
exemplos utilizam a classe Nette\Object
, mas a maioria das propriedades também se aplica ao traço
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()?"
O PHP de hoje pode não ter nenhuma forma de “você quis dizer?”, mas Tracy acrescenta este adendo aos erros. E ele pode até corrigir tais erros por si só.
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);