Nette Documentation Preview

syntax
SmartObject and StaticClass
***************************

.[perex]
SmartObject adds support for *property* to PHP classes. StaticClass is used to denote static classes.

Installation:

```shell
composer require nette/utils
```


Properties, Getters and Setters
===============================

In modern object-oriented languages (e.g. C#, Python, Ruby, JavaScript), the term *property* refers to [special members of classes |https://en.wikipedia.org/wiki/Property_(programming)] that look like variables but are actually represented by methods. When the value of this "variable" is assigned or read, the corresponding method (called getter or setter) is called. This is a very handy thing to do, it gives us full control over access to variables. We can validate the input or generate results only when the property is read.

PHP properties are not supported, but trait `Nette\SmartObject` can imitate them. How to use it?

- Add an annotation to the class in the form `@property <type> $xyz`
- Create a getter named `getXyz()` or `isXyz()`, a setter named `setXyz()`
- The getter and setter must be *public* or *protected* and are optional, so there can be a *read-only* or *write-only* property

We will use the property for the Circle class to ensure that only non-negative numbers are put into the `$radius` variable. Replace `public $radius` with property:

```php
/**
 * @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()
```

Properties are primarily "syntactic sugar"((syntactic sugar)), which is intended to make the programmer's life sweeter by simplifying the code. If you don't want them, you don't have to use them.


Static Classes
==============

Static classes, i.e. classes that are not intended to be instantiated, can be marked with the trait `Nette\StaticClass`:

```php
class Strings
{
	use Nette\StaticClass;
}
```

When you try to create an instance, the `Error` exception is thrown, indicating that the class is static.


A Look into the History
=======================

SmartObject used to improve and fix class behavior in many ways, but the evolution of PHP has made most of the original features redundant. So the following is a look into the history of how things have evolved.

From the beginning, the PHP object model suffered from a number of serious flaws and inefficiencies. This was the reason for the creation of the `Nette\Object` class (in 2007), which attempted to remedy them and improve the experience of using PHP. It was enough for other classes to inherit from it, and gain the benefits it brought. When PHP 5.4 came with trait support, the `Nette\Object` class was replaced by `Nette\SmartObject`. Thus, it was no longer necessary to inherit from a common ancestor. In addition, trait could be used in classes that already inherited from another class. The final end of `Nette\Object` came with the release of PHP 7.2, which forbade classes to be named `Object`.

As PHP development went on, the object model and language capabilities were improved. The individual functions of the `SmartObject` class became redundant. Since the release of PHP 8.2, the only feature that remains that is not yet directly supported in PHP is the ability to use so-called [properties |#Properties, Getters and Setters].

What features did `Nette\Object` and `Nette\Object` once offer? Here is an overview. (The examples use the `Nette\Object` class, but most of the properties also apply to the `Nette\SmartObject` trait.)


Inconsistent Errors
-------------------
PHP had inconsistent behavior when accessing undeclared members. The state at the time of `Nette\Object` was as follows:

```php
echo $obj->undeclared; // E_NOTICE, later E_WARNING
$obj->undeclared = 1;  // passes silently without reporting
$obj->unknownMethod(); // Fatal error (not catchable by try/catch)
```

Fatal error terminated the application without any possibility to react. Silently writing to non-existent members without warning could lead to serious errors that were difficult to detect. `Nette\Object` All of these cases were caught and an exception `MemberAccessException` was thrown.

```php
echo $obj->undeclared;   // throw Nette\MemberAccessException
$obj->undeclared = 1;    // throw Nette\MemberAccessException
$obj->unknownMethod();   // throw Nette\MemberAccessException
```
Since PHP 7.0, PHP no longer causes not catchable fatal errors, and accessing undeclared members has been a bug since PHP 8.2.


Did you mean?
-------------
If an `Nette\MemberAccessException` error was thrown, perhaps due to a typo when accessing an object variable or calling a method, `Nette\Object` attempted to give a hint in the error message on how to fix the error, in the form of the iconic "did you mean?" addendum.

```php
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()?"
```

Today's PHP may not have any form of "did you mean?", but [Tracy |tracy:] adds this addendum to errors. And it can even [fix |tracy:open-files-in-ide#toc-demos] such errors itself.


Extension methods
-----------------
Inspired by extension methods from C#. They gave the possibility to add new methods to existing classes. For example, you could add the `addDateTime()` method to a form to add your own DateTimePicker.

```php
Form::extensionMethod(
	'addDateTime',
	fn(Form $form, string $name) => $form[$name] = new DateTimePicker,
);

$form = new Form;
$form->addDateTime('date');
```

Extension methods proved to be impractical because their names was not autocompleted by editors, instead they reported that the method did not exist. Therefore, their support was discontinued.


Getting the Class Name
----------------------

```php
$class = $obj->getClass(); // using Nette\Object
$class = $obj::class;      // since PHP 8.0
```


Access to Reflection and Annotations
------------------------------------

`Nette\Object` offered access to reflection and annotation using the methods `getReflection()` and `getAnnotation()`:

```php
/**
 * @author John Doe
 */
class Foo extends Nette\Object
{
}

$obj = new Foo;
$reflection = $obj->getReflection();
$reflection->getAnnotation('author'); // returns 'John Doe
```

As of PHP 8.0, it is possible to access meta-information in the form of attributes:

```php
#[Author('John Doe')]
class Foo
{
}

$obj = new Foo;
$reflection = new ReflectionObject($obj);
$reflection->getAttributes(Author::class)[0];
```


Method Getters
--------------

`Nette\Object` offered an elegant way to deal with methods as if they were variables:

```php
class Foo extends Nette\Object
{
	public function adder($a, $b)
	{
		return $a + $b;
	}
}

$obj = new Foo;
$method = $obj->adder;
echo $method(2, 3); // 5
```

As of PHP 8.1, you can use the so-called "first-class callable syntax":https://www.php.net/manual/en/functions.first_class_callable_syntax:

```php
$obj = new Foo;
$method = $obj->adder(...);
echo $method(2, 3); // 5
```


Events
------

`Nette\Object` offered syntactic sugar to trigger the [event |nette:glossary#events]:

```php
class Circle extends Nette\Object
{
	public array $onChange = [];

	public function setRadius(float $radius): void
	{
		$this->onChange($this, $radius);
		$this->radius = $radius
	}
}
```

The code `$this->onChange($this, $radius)` is equivalent to the following:

```php
foreach ($this->onChange as $callback) {
	$callback($this, $radius);
}
```

For the sake of clarity we recommend to avoid the magic method `$this->onChange()`. A practical substitute is the [Nette\Utils\Arrays::invoke |arrays#invoke] function:

```php
Nette\Utils\Arrays::invoke($this->onChange, $this, $radius);
```

SmartObject and StaticClass

SmartObject adds support for property to PHP classes. StaticClass is used to denote static classes.

Installation:

composer require nette/utils

Properties, Getters and Setters

In modern object-oriented languages (e.g. C#, Python, Ruby, JavaScript), the term property refers to special members of classes that look like variables but are actually represented by methods. When the value of this „variable“ is assigned or read, the corresponding method (called getter or setter) is called. This is a very handy thing to do, it gives us full control over access to variables. We can validate the input or generate results only when the property is read.

PHP properties are not supported, but trait Nette\SmartObject can imitate them. How to use it?

  • Add an annotation to the class in the form @property <type> $xyz
  • Create a getter named getXyz() or isXyz(), a setter named setXyz()
  • The getter and setter must be public or protected and are optional, so there can be a read-only or write-only property

We will use the property for the Circle class to ensure that only non-negative numbers are put into the $radius variable. Replace public $radius with property:

/**
 * @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()

Properties are primarily syntactic sugar, which is intended to make the programmer's life sweeter by simplifying the code. If you don't want them, you don't have to use them.

Static Classes

Static classes, i.e. classes that are not intended to be instantiated, can be marked with the trait Nette\StaticClass:

class Strings
{
	use Nette\StaticClass;
}

When you try to create an instance, the Error exception is thrown, indicating that the class is static.

A Look into the History

SmartObject used to improve and fix class behavior in many ways, but the evolution of PHP has made most of the original features redundant. So the following is a look into the history of how things have evolved.

From the beginning, the PHP object model suffered from a number of serious flaws and inefficiencies. This was the reason for the creation of the Nette\Object class (in 2007), which attempted to remedy them and improve the experience of using PHP. It was enough for other classes to inherit from it, and gain the benefits it brought. When PHP 5.4 came with trait support, the Nette\Object class was replaced by Nette\SmartObject. Thus, it was no longer necessary to inherit from a common ancestor. In addition, trait could be used in classes that already inherited from another class. The final end of Nette\Object came with the release of PHP 7.2, which forbade classes to be named Object.

As PHP development went on, the object model and language capabilities were improved. The individual functions of the SmartObject class became redundant. Since the release of PHP 8.2, the only feature that remains that is not yet directly supported in PHP is the ability to use so-called properties.

What features did Nette\Object and Nette\Object once offer? Here is an overview. (The examples use the Nette\Object class, but most of the properties also apply to the Nette\SmartObject trait.)

Inconsistent Errors

PHP had inconsistent behavior when accessing undeclared members. The state at the time of Nette\Object was as follows:

echo $obj->undeclared; // E_NOTICE, later E_WARNING
$obj->undeclared = 1;  // passes silently without reporting
$obj->unknownMethod(); // Fatal error (not catchable by try/catch)

Fatal error terminated the application without any possibility to react. Silently writing to non-existent members without warning could lead to serious errors that were difficult to detect. Nette\Object All of these cases were caught and an exception MemberAccessException was thrown.

echo $obj->undeclared;   // throw Nette\MemberAccessException
$obj->undeclared = 1;    // throw Nette\MemberAccessException
$obj->unknownMethod();   // throw Nette\MemberAccessException

Since PHP 7.0, PHP no longer causes not catchable fatal errors, and accessing undeclared members has been a bug since PHP 8.2.

Did you mean?

If an Nette\MemberAccessException error was thrown, perhaps due to a typo when accessing an object variable or calling a method, Nette\Object attempted to give a hint in the error message on how to fix the error, in the form of the iconic „did you mean?“ addendum.

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()?"

Today's PHP may not have any form of „did you mean?“, but Tracy adds this addendum to errors. And it can even fix such errors itself.

Extension methods

Inspired by extension methods from C#. They gave the possibility to add new methods to existing classes. For example, you could add the addDateTime() method to a form to add your own DateTimePicker.

Form::extensionMethod(
	'addDateTime',
	fn(Form $form, string $name) => $form[$name] = new DateTimePicker,
);

$form = new Form;
$form->addDateTime('date');

Extension methods proved to be impractical because their names was not autocompleted by editors, instead they reported that the method did not exist. Therefore, their support was discontinued.

Getting the Class Name

$class = $obj->getClass(); // using Nette\Object
$class = $obj::class;      // since PHP 8.0

Access to Reflection and Annotations

Nette\Object offered access to reflection and annotation using the methods getReflection() and getAnnotation():

/**
 * @author John Doe
 */
class Foo extends Nette\Object
{
}

$obj = new Foo;
$reflection = $obj->getReflection();
$reflection->getAnnotation('author'); // returns 'John Doe

As of PHP 8.0, it is possible to access meta-information in the form of attributes:

#[Author('John Doe')]
class Foo
{
}

$obj = new Foo;
$reflection = new ReflectionObject($obj);
$reflection->getAttributes(Author::class)[0];

Method Getters

Nette\Object offered an elegant way to deal with methods as if they were 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

As of PHP 8.1, you can use the so-called first-class callable syntax:

$obj = new Foo;
$method = $obj->adder(...);
echo $method(2, 3); // 5

Events

Nette\Object offered syntactic sugar to trigger the event:

class Circle extends Nette\Object
{
	public array $onChange = [];

	public function setRadius(float $radius): void
	{
		$this->onChange($this, $radius);
		$this->radius = $radius
	}
}

The code $this->onChange($this, $radius) is equivalent to the following:

foreach ($this->onChange as $callback) {
	$callback($this, $radius);
}

For the sake of clarity we recommend to avoid the magic method $this->onChange(). A practical substitute is the Nette\Utils\Arrays::invoke function:

Nette\Utils\Arrays::invoke($this->onChange, $this, $radius);