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() ou isXyz(), um setter chamado setXyz()
  • 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);
versão: 4.0