1
0
mirror of https://github.com/danog/amp.git synced 2024-12-02 09:27:46 +01:00

Remove outdated docs

This commit is contained in:
Niklas Keller 2021-12-02 23:38:17 +01:00
parent 6055ace6c9
commit 19937d75d0
9 changed files with 11 additions and 727 deletions

View File

@ -6,27 +6,24 @@ baseurl: "/amp"
layouts_dir: ".shared/layout" layouts_dir: ".shared/layout"
includes_dir: ".shared/includes" includes_dir: ".shared/includes"
exclude: ["Gemfile", "Gemfile.lock", "README.md", "vendor"] exclude: [ "Gemfile", "Gemfile.lock", "README.md", "vendor" ]
safe: true safe: true
repository: amphp/amp repository: amphp/amp
gems: gems:
- "jekyll-github-metadata" - "jekyll-github-metadata"
- "jekyll-relative-links" - "jekyll-relative-links"
defaults: defaults:
- scope: - scope:
path: "" path: ""
type: "pages" type: "pages"
values: values:
layout: "docs" layout: "docs"
shared_asset_path: "/amp/asset" shared_asset_path: "/amp/asset"
navigation: navigation:
- event-loop - promises
- promises - coroutines
- coroutines - cancellation
- iterators
- cancellation
- utils

View File

@ -1,115 +0,0 @@
---
layout: docs
title: Event Loop
permalink: /event-loop/
---
It may surprise people to learn that the PHP standard library already has everything we need to write event-driven and non-blocking applications. We only reach the limits of native PHP's functionality in this area when we ask it to poll thousands of file descriptors for IO activity at the same time. Even in this case, though, the fault is not with PHP but the underlying system `select()` call which is linear in its performance degradation as load increases.
For performance that scales out to high volume we require more advanced capabilities currently found only in extensions. If you wish to, for example, service 10,000 simultaneous clients in an Amp-backed socket server, you should use one of the event loop implementations based on a PHP extension. However, if you're using Amp in a strictly local program for non-blocking concurrency or you don't need to handle more than a few hundred simultaneous clients in a server application the native PHP functionality should be adequate.
## Global Accessor
Amp uses a global accessor for the event loop as there's only one event loop for each application. It doesn't make sense to have two loops running at the same time, as they would just have to schedule each other in a busy waiting manner to operate correctly.
The event loop should be accessed through the methods provided by `Amp\Loop`. On the first use of the accessor, Amp will automatically setup the best available driver, see next section.
`Amp\Loop::set()` can be used to set a custom driver or to reset the driver in tests, as each test should run with a fresh driver instance to achieve test isolation. In case of PHPUnit, you can use a [`TestListener` to reset the event loop](https://github.com/amphp/phpunit-util) automatically after each tests.
## Implementations
Amp offers different event loop implementations based on various backends. All implementations extend `Amp\Loop\Driver`. Each behaves exactly the same way from an external API perspective. The main differences have to do with underlying performance characteristics. The current implementations are listed here:
| Class | Extension | Repository |
| ------------------------- | ------------------------------------------------------ | ---------- |
| `Amp\Loop\NativeDriver` | | - |
| `Amp\Loop\EvDriver` | [`pecl/ev`](https://pecl.php.net/package/ev) | [`php-ev`](https://bitbucket.org/osmanov/pecl-ev) |
| `Amp\Loop\EventDriver` | [`pecl/event`](https://pecl.php.net/package/event) | [`pecl-event`](https://bitbucket.org/osmanov/pecl-event) |
| `Amp\Loop\UvDriver` | [`pecl/uv`](https://pecl.php.net/package/uv) | [`php-uv`](https://github.com/bwoebi/php-uv) |
It's not important to choose one implementation for your application. Amp will automatically select the best available driver. It's perfectly fine to have one of the extensions in production while relying on the `NativeDriver` locally for development.
If you want to quickly switch implementations during development, e.g. for comparison or testing, you can set the `AMP_LOOP_DRIVER` environment variable to one of the classes. If you use a custom implementation, this only works if the implementation doesn't take any arguments.
## Event Loop as Task Scheduler
The first thing we need to understand to program effectively using an event loop is this:
**The event loop is our task scheduler.**
The event loop controls the program flow as long as it runs. Once we tell the event loop to run it will maintain control until the application errors out, has nothing left to do, or is explicitly stopped.
Consider this very simple example:
```php
<?php
require "vendor/autoload.php";
use Amp\Loop;
function tick() {
echo "tick\n";
}
echo "-- before Loop::run()\n";
Loop::run(function() {
Loop::repeat($msInterval = 1000, "tick");
Loop::delay($msDelay = 5000, "Amp\\Loop::stop");
});
echo "-- after Loop::run()\n";
```
Upon execution of the above example you should see output like this:
```plain
-- before Loop::run()
tick
tick
tick
tick
-- after Loop::run()
```
This output demonstrates that what happens inside the event loop's run loop is like its own separate program. Your script will not continue past the point of `Loop::run()` unless there are no more scheduled events or `Loop::stop()` is invoked.
While an application can and often does take place entirely inside the confines of the run loop, we can also use the event loop to do things like the following example which imposes a short-lived timeout for interactive console input:
```php
<?php
use Amp\Loop;
$myText = null;
function onInput($watcherId, $stream)
{
global $myText;
$myText = fgets($stream);
stream_set_blocking(STDIN, true);
Loop::cancel($watcherId);
Loop::stop();
}
Loop::run(function () {
echo "Please input some text: ";
stream_set_blocking(STDIN, false);
// Watch STDIN for input
Loop::onReadable(STDIN, "onInput");
// Impose a 5-second timeout if nothing is input
Loop::delay($msDelay = 5000, "Amp\\Loop::stop");
});
var_dump($myText); // whatever you input on the CLI
// Continue doing regular synchronous things here.
```
Obviously we could have simply used `fgets(STDIN)` synchronously in this example. We're just demonstrating that it's possible to move in and out of the event loop to mix synchronous tasks with non-blocking tasks as needed.
Continue with the [Event Loop API](./api.md).

View File

@ -1,416 +0,0 @@
---
layout: docs
title: Event Loop API
permalink: /event-loop/api
---
This document describes the [`Amp\Loop`](https://github.com/amphp/amp/blob/master/lib/Loop.php) accessor. You might want to also read the documentation contained in the source file, it's extensively documented and doesn't contain much distracting code.
## `run()`
The primary way an application interacts with the event loop is to schedule events for execution and then simply let the program run. Once `Loop::run()` is invoked the event loop will run indefinitely until there are no watchable timer events, IO streams or signals remaining to watch. Long-running programs generally execute entirely inside the confines of a single `Loop::run()` call.
`Loop::run()` accepts an optional callback as first parameter. Passing such a callback is equivalent to calling `Loop::defer($callback)` and `Loop::run()` afterwards.
## `stop()`
The event loop can be stopped at any time while running. When `Loop::stop()` is invoked the event loop will return control to the userland script at the end of the current tick of the event loop. This method may be used to yield control from the event loop even if events or watchable IO streams are still pending.
## `now()`
Returns the current 'loop time' in millisecond increments. The value returned by this method does not necessarily correlate to wall-clock time, rather the value is meant to be used in relative comparisons to prior values returned by this method (e.g.: interval calculations, expiration times, etc.). The value returned by this method is only updated once per loop tick.
## Timer Watchers
Amp exposes several ways to schedule timer watchers. Let's look at some details for each function.
### `defer()`
- Schedules a callback to execute in the next iteration of the event loop
- This method guarantees a clean call stack to avoid starvation of other events in the current iteration of the loop. An `defer` callback is *always* executed in the next tick of the event loop.
- After an `defer` timer watcher executes it is automatically garbage collected by the event loop so there is no need for applications to manually cancel the associated watcher.
- Like all watchers, `defer` timers may be disabled and re-enabled. If you disable this watcher between the time you schedule it and the time that it actually runs the event loop *will not* be able to garbage collect it until it executes. Therefore you must manually cancel an `defer` watcher yourself if it never actually executes to free any associated resources.
**Example**
```php
<?php // using Loop::defer()
use Amp\Loop;
Loop::run(function () {
echo "line 1\n";
Loop::defer(function () {
echo "line 3\n";
});
echo "line 2\n";
});
```
**Callback Signature**
`function (string $watcherId, mixed $cbData = null)`
### `delay()`
- Schedules a callback to execute after a delay of `n` milliseconds
- A "delay" watcher is also automatically garbage collected by the reactor after execution and applications should not manually cancel it unless they wish to discard the watcher entirely prior to execution.
- A "delay" watcher that is disabled has its delay time reset so that the original delay time starts again from zero once re-enabled.
- Like `defer` watchers, a timer scheduled for one-time execution must be manually canceled to free resources if it never runs due to being disabled by the application after creation.
**Example**
```php
<?php // using delay()
use Amp\Loop;
Loop::run(function () {
// event loop will stop in three seconds
Loop::delay($msDelay = 3000, "Amp\\Loop::stop");
});
```
**Callback Signature**
`function (string $watcherId, mixed $cbData = null)`
### `repeat()`
- Schedules a callback to repeatedly execute every `n` milliseconds.
- Like all other watchers, `repeat` timers may be disabled/re-enabled at any time.
- Unlike `defer()` and `delay()` watchers, `repeat()` timers must be explicitly canceled to free associated resources. Failure to free `repeat` watchers via `cancel()` once their purpose is fulfilled will result in memory leaks in your application. It is not enough to simply disable repeat watchers as their data is only freed upon cancellation.
```php
<?php // using repeat()
use Amp\Loop;
Loop::run(function () {
Loop::repeat($msInterval = 100, function ($watcherId) {
static $i = 0;
if ($i++ < 3) {
echo "tick\n";
} else {
Loop::cancel($watcherId);
}
});
});
```
**Callback Signature**
`function (string $watcherId, mixed $cbData = null)`
## Stream IO Watchers
Stream watchers are how we know when we can read and write to sockets and other streams. These events are how we're able to actually create things like HTTP servers and asynchronous database libraries using the event loop. As such, stream IO watchers form the backbone of any useful non-blocking Amp application.
There are two types of IO watchers:
- Readability watchers
- Writability watchers
### `onReadable()`
{:.note}
> This is an advanced low-level API. Most users should use [`amphp/byte-stream`](https://github.com/amphp/byte-stream) instead.
Watchers registered via `Loop::onReadable()` trigger their callbacks in the following situations:
- When data is available to read on the stream under observation
- When the stream is at EOF (for sockets, this means the connection is broken)
A common usage pattern for reacting to readable data looks something like this example:
```php
<?php
use Amp\Loop;
const IO_GRANULARITY = 32768;
function isStreamDead($socket) {
return !is_resource($socket) || @feof($socket);
}
Loop::onReadable($socket, function ($watcherId, $socket) {
$socketId = (int) $socket;
$newData = @fread($socket, IO_GRANULARITY);
if ($newData != "") {
// There was actually data and not an EOF notification. Let's consume it!
parseIncrementalData($socketId, $newData);
} elseif (isStreamDead($socket)) {
Loop::cancel($watcherId);
}
});
```
In the above example we've done a few very simple things:
- Register a readability watcher for a socket that will trigger our callback when there is data available to read.
- When we read data from the stream in our triggered callback we pass that to a stateful parser that does something domain-specific when certain conditions are met.
- If the `fread()` call indicates that the socket connection is dead we clean up any resources we've allocated for the storage of this stream. This process should always include calling `Loop::cancel()` on any event loop watchers we registered in relation to the stream.
{:.warning}
> You should always read a multiple of the configured chunk size (default: 8192), otherwise your code might not work as expected with loop backends other than `stream_select()`, see [amphp/amp#65](https://github.com/amphp/amp/issues/65) for more information.
### `onWritable()`
{:.note}
> This is an advanced low-level API. Most users should use [`amphp/byte-stream`](https://github.com/amphp/byte-stream) instead.
- Streams are essentially *"always"* writable. The only time they aren't is when their respective write buffers are full.
A common usage pattern for reacting to writability involves initializing a writability watcher without enabling it when a client first connects to a server. Once incomplete writes occur we're then able to "unpause" the write watcher using `Loop::enable()` until data is fully sent without having to create and cancel new watcher resources on the same stream multiple times.
## Pausing, Resuming and Canceling Watchers
All watchers, regardless of type, can be temporarily disabled and enabled in addition to being cleared via `Loop::cancel()`. This allows for advanced capabilities such as disabling the acceptance of new socket clients in server applications when simultaneity limits are reached. In general, the performance characteristics of watcher reuse via pause/resume are favorable by comparison to repeatedly canceling and re-registering watchers.
### `disable()`
A simple disable example:
```php
<?php
use Amp\Loop;
// Register a watcher we'll disable
$watcherIdToDisable = Loop::delay($msDelay = 1000, function () {
echo "I'll never execute in one second because: disable()\n";
});
// Register a watcher to perform the disable() operation
Loop::delay($msDelay = 500, function () use ($watcherIdToDisable) {
echo "Disabling WatcherId: ", $watcherIdToDisable, "\n";
Loop::disable($watcherIdToDisable);
});
Loop::run();
```
After our second watcher callback executes the event loop exits because there are no longer any enabled watchers registered to process.
### `enable()`
`enable()` is the diametric analog of the `disable()` example demonstrated above:
```php
<?php
use Amp\Loop;
// Register a watcher
$myWatcherId = Loop::repeat($msInterval = 1000, function() {
echo "tick\n";
});
// Disable the watcher
Loop::disable($myWatcherId);
// Remember, nothing happens until the event loop runs, so it doesn't matter that we
// previously created and disabled $myWatcherId
Loop::run(function () use ($myWatcherId) {
// Immediately enable the watcher when the reactor starts
Loop::enable($myWatcherId);
// Now that it's enabled we'll see tick output in our console every 1000ms.
});
```
For a slightly more complex use case, let's look at a common scenario where a server might create a write watcher that is initially disabled but subsequently enabled as necessary:
```php
<?php
use Amp\Loop;
class Server
{
private $clients = [];
public function startServer()
{
// ... server bind and accept logic would exist here
Loop::run();
}
private function onNewClient($sock)
{
$socketId = (int) $sock;
$client = new ClientStruct;
$client->socket = $sock;
$readWatcher = Loop::onReadable($sock, function () use ($client) {
$this->onReadable($client);
});
$writeWatcher = Loop::onWritable($sock, function () use ($client) {
$this->doWrite($client);
});
Loop::disable($writeWatcher); // <-- let's initialize the watcher as "disabled"
$client->readWatcher = $readWatcher;
$client->writeWatcher = $writeWatcher;
$this->clients[$socketId] = $client;
}
// ... other class implementation details here ...
private function writeToClient($client, $data)
{
$client->writeBuffer .= $data;
$this->doWrite($client);
}
private function doWrite(ClientStruct $client)
{
$bytesToWrite = strlen($client->writeBuffer);
$bytesWritten = @fwrite($client->socket, $client->writeBuffer);
if ($bytesToWrite === $bytesWritten) {
Loop::disable($client->writeWatcher);
} elseif ($bytesWritten >= 0) {
$client->writeBuffer = substr($client->writeBuffer, $bytesWritten);
Loop::enable($client->writeWatcher);
} elseif ($this->isSocketDead($client->socket)) {
$this->unloadClient($client);
}
}
// ... other class implementation details here ...
}
```
### `cancel()`
It's important to *always* cancel persistent watchers once you're finished with them or you'll create memory leaks in your application. This functionality works in exactly the same way as the above `enable` / `disable` examples:
```php
<?php
use Amp\Loop;
Loop::run(function() {
$myWatcherId = Loop::repeat($msInterval = 1000, function () {
echo "tick\n";
});
// Cancel $myWatcherId in five seconds and exit the event loop
Loop::delay($msDelay = 5000, function () use ($myWatcherId) {
Loop::cancel($myWatcherId);
});
});
```
## `onSignal()`
`Loop::onSignal()` can be used to react to signals sent to the process.
```php
<?php
use Amp\Loop;
Loop::run(function () {
// Let's tick off output once per second so we can see activity.
Loop::repeat($msInterval = 1000, function () {
echo "tick: ", date('c'), "\n";
});
// What to do when a SIGINT signal is received
$watcherId = Loop::onSignal(UV::SIGINT, function () {
echo "Caught SIGINT! exiting ...\n";
exit;
});
});
```
As should be clear from the above example, signal watchers may be enabled, disabled and canceled like any other event.
## Referencing Watchers
Watchers can either be referenced or unreferenced. An unreferenced watcher doesn't keep the loop alive. All watchers are referenced by default.
One example to use unreferenced watchers is when using signal watchers. Generally, if all watchers are gone and only the signal watcher still exists, you want to exit the loop as you're not actively waiting for that event to happen.
### `reference()`
Marks a watcher as referenced. Takes the `$watcherId` as first and only argument.
### `unreference()`
Marks a watcher as unreferenced. Takes the `$watcherId` as first and only argument.
## Driver Bound State
Sometimes it's very handy to have global state. While dependencies should usually be injected, it is impracticable to pass a `DnsResolver` into everything that needs a network connection. The `Loop` accessor provides therefore the two methods `getState` and `setState` to store state global to the current event loop driver.
These should be used with care! They can be used to store loop bound singletons such as the DNS resolver, filesystem driver, or global `ReactAdapter`. Applications should generally not use these methods.
## Event Loop Addenda
### Watcher Callback Parameters
Watcher callbacks are invoked using the following standardized parameter order:
| Watcher Type | Callback Signature |
| ----------------------- | ------------------------------------------------------|
| `defer()` | `function(string $watcherId, $callbackData)` |
| `delay()` | `function(string $watcherId, $callbackData)` |
| `repeat()` | `function(string $watcherId, $callbackData)` |
| `onReadable()` | `function(string $watcherId, $stream, $callbackData)` |
| `onWritable()` | `function(string $watcherId, $stream, $callbackData)` |
| `onSignal()` | `function(string $watcherId, $signo, $callbackData)` |
### Watcher Cancellation Safety
It is always safe to cancel a watcher from within its own callback. For example:
```php
<?php
use Amp\Loop;
$increment = 0;
Loop::repeat($msDelay = 50, function ($watcherId) use (&$increment) {
echo "tick\n";
if (++$increment >= 3) {
Loop::cancel($watcherId); // <-- cancel myself!
}
});
```
It is also always safe to cancel a watcher from multiple places. A double-cancel will simply be ignored.
### An Important Note on Writability
Because streams are essentially *"always"* writable you should only enable writability watchers while you have data to send. If you leave these watchers enabled when your application doesn't have anything to write the watcher will trigger endlessly until disabled or canceled. This will max out your CPU. If you're seeing inexplicably high CPU usage in your application it's a good bet you've got a writability watcher that you failed to disable or cancel after you were finished with it.
A standard pattern in this area is to initialize writability watchers in a disabled state before subsequently enabling them at a later time as shown here:
```php
<?php
use Amp\Loop;
$watcherId = Loop::onWritable(STDOUT, function () {});
Loop::disable($watcherId);
// ...
Loop::enable($watcherId);
// ...
Loop::disable($watcherId);
```
### Process Signal Number Availability
`php-uv` exposes `UV::SIG*` constants for watchable signals. Applications using the `EventDriver` will need to manually specify the appropriate integer signal numbers when registering signal watchers.
[libevent]: http://pecl.php.net/package/libevent "libevent"
[win-libevent]: http://windows.php.net/downloads/pecl/releases/ "Windows libevent DLLs"
### Timer Drift
Repeat timers are basically simple delay timers that are automatically rescheduled right before the appropriate handler is triggered. They are subject to timer drift. Multiple timers might stack up in case they execute as coroutines.

View File

@ -1,74 +0,0 @@
---
layout: docs
title: Iterators
permalink: /iterators/
---
Iterators are the next level after promises. While promises resolve once and with one value, iterators allow a set of items to be consumed.
## Iterator Consumption
Every iterator in Amp follows the `Amp\Iterator` interface.
```php
namespace Amp;
interface Iterator
{
public function advance(): Promise;
public function getCurrent();
}
```
`advance()` returns a `Promise` and its resolution value tells whether there's an element to consume or not. If it resolves to `true`, `getCurrent()` can be used to consume the element at the current position, otherwise the iterator ended and there are no more values to consume. In case an exception happens, `advance()` returns a failed promise and `getCurrent()` throws the failure reason when called.
### Simple Consumption Example
```php
$iterator = foobar();
while (yield $iterator->advance()) {
$element = $iterator->getCurrent();
// do something with $element
}
```
## Iterator Creation
### Emitter
What `Deferred` is for promises, is `Emitter` for iterators. A library that returns an `Iterator` for asynchronous consumption of an iterable result creates an `Amp\Emitter` and returns the `Iterator` using `iterate()`. This ensures a consumer can only consume the iterator, but not emit values or complete the iterator.
#### `emit()`
`emit()` emits a new value to the `Iterator`, which can be consumed by a consumer. The emitted value is passed as first argument to `emit()`. `emit()` returns a `Promise` that can be waited on before emitting new values. This allow emitting values just as fast as the consumer can consume them.
#### `complete()`
`complete()` marks the `Emitter` / linked `Iterator` as complete. No further emits are allowed after completing an `Emitter` / `Iterator`.
### Producer
`Producer` is a simplified form of `Emitter` that can be used when a single coroutine can emit all values.
`Producer` accepts a `callable` as first constructor parameter that gets run as a coroutine and passed an `$emit` callable that can be used to emit values just like the `emit()` method does in `Emitter`.
#### Example
```php
$iterator = new Producer(function (callable $emit) {
yield $emit(1);
yield $emit(new Delayed(500, 2));
yield $emit(3);
yield $emit(4);
});
```
### `fromIterable`
Iterators can also be created from ordinary PHP arrays or `Traversable` instances, which is mainly useful in tests, but might also be used for the same reasons as `Success` and `Failure`.
```php
function fromIterable($iterable, int $delay = 0) { ... }
```
`$delay` allows adding a delay between each emission.

View File

@ -1,14 +0,0 @@
---
layout: docs
title: Iterator Combination
permalink: /iterators/combinators
---
Amp provides two common combination helpers for iterators: `concat` and `merge`.
## `concat()`
`concat()` accepts an array of `Iterator` instances and concatenates the given iterators into a single iterator, emitting values from a single iterator at a time. The prior iterator must complete before values are emitted from any subsequent iterators. Iterators are concatenated in the order given (iteration order of the array).
## `merge()`
`merge()` accepts an array of `Iterator` instances and creates an `Iterator` that emits values emitted from any iterator in the array of iterators ending once all emitters completed.

View File

@ -1,20 +0,0 @@
---
layout: docs
title: Iterator Transformation
permalink: /iterators/transformation
---
Amp provides some common transformation helpers for iterators: `map`, `filter` and `toArray`.
Further primitives are very easy to implement using `Producer` with these as examples.
## `map()`
`map()` accepts an `Iterator` and a `callable` `$onEmit` that can transform each value into another value.
## `filter()`
`filter()` accepts an `Iterator` and a `callable` `$filter`. If `$filter($value)` returns `false` the value gets filtered, otherwise the value is retained in the resulting `Iterator`.
## `toArray()`
`toArray()` accepts an `Iterator` and returns a `Promise` which resolves to an array of all the items from the iterator.

View File

@ -1,11 +0,0 @@
---
layout: docs
title: Utils
permalink: /utils/
---
This documentation section deals with helpers that are not async specific, but generic helpers.
* [`CallableMaker`](./callable-maker.md)
* [`Struct`](./struct.md)
Further utils for PHPUnit are provided in [`amphp/phpunit-util`](https://github.com/amphp/phpunit-util).

View File

@ -1,16 +0,0 @@
---
layout: docs
title: CallableMaker
permalink: /utils/callable-maker
---
`Amp\CallableMaker` is a helper trait that allows creating closures from private / protected static and instance methods in an easy way. Creating such callables might be necessary to register private / protected methods as callbacks in an efficient manner without making those methods public.
This trait should only be used in projects with a PHP 7.0 minimum requirement. If PHP 7.1 or later are the minimum requirement, `Closure::fromCallable` should be used directly.
## `callableFromInstanceMethod()`
Creates a `Closure` form an instance method with the given name and returns it. The closure can be passed around without worrying about the method's visibility.
## `callableFromStaticMethod()`
Same as `callableFromInstanceMethod()`, but for static methods.

View File

@ -1,47 +0,0 @@
---
layout: docs
title: Struct
permalink: /utils/struct
---
A struct is a generic computer science term for an object composed of public properties. The `\Amp\Struct` trait
is intended to make using public properties a little safer by throwing an `\Error` when undefined properties
are attempted to be written or read.
PHP allows for dynamic creation of public properties. This can lead to insidious bugs created by typos related to
writing to and reading from public properties. One common solution to this problem is to make all properties private and
provide public setter and getter methods which control access to the underlying properties. However effective this
solution may be, it requires that additional code be written and subsequently tested for the setter and getter methods.
Let's try some examples with anonymous classes to demonstrate the advantages of using the `\Amp\Struct` trait. Running
the following code will not error; although, the typo will likely create some unexpected behavior:
```php
$obj = new class {
public $foo = null;
};
$obj->fooo = "bar";
```
If you were to access the `$foo` property of the `$obj` object after the above code, you might expect the value
to be `"bar"` when it would actually be `NULL`.
When a class uses the `\Amp\Struct` trait, an `\Error` will be thrown when attempting to access a property not defined
in the class definition. For example, the code below will throw an `\Error` with some context that attempts to help
diagnose the issue.
```php
$obj = new class {
use Amp\Struct;
public $foo = null;
};
$obj->fooo = "bar";
```
The message for the thrown `\Error` will be similar to:
*Uncaught Error: class@anonymous@php shell code0x10ee8005b property "fooo" does not exist ... did you mean "foo?"*
Although, an `\Error` being thrown in your code may cause some havoc, it will not allow for unpredictable
behavior caused by the use of properties which are not part of the class definition.