PHPackages                             cosmicpe/awaitform - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Utility &amp; Helpers](/categories/utility)
4. /
5. cosmicpe/awaitform

ActiveLibrary[Utility &amp; Helpers](/categories/utility)

cosmicpe/awaitform
==================

Write clean form navigation flows in PocketMine-MP using async/await pattern

v0.0.3(9mo ago)131352[1 issues](https://github.com/Cosmoverse/AwaitForm/issues)1GPL-3.0-onlyPHPPHP ^8.3

Since Feb 22Pushed 9mo ago2 watchersCompare

[ Source](https://github.com/Cosmoverse/AwaitForm)[ Packagist](https://packagist.org/packages/cosmicpe/awaitform)[ RSS](/packages/cosmicpe-awaitform/feed)WikiDiscussions master Synced 1mo ago

READMEChangelog (3)Dependencies (2)Versions (4)Used By (1)

AwaitForm
=========

[](#awaitform)

Write clean form navigation flows in PocketMine-MP using async/await pattern in PHP.

Motive
------

[](#motive)

Form navigation flows are inherently asynchronous. Existing libraries use callbacks or specialized Form classes to handle responses. Control flow syntax (e.g., while, for, continue, break) cannot be fully utilized as each form handler gets its own isolated context. This makes several tasks challenging.

Navigation flow becomes incomprehensible in code when Form A sends user to Form B before bringing them back to Form A, but this time with different parameters for Form A. This is often encountered with pagination ('Previous Page' and 'Next Page' buttons), refresh mechanisms (e.g., a 'Refresh' button), and non-dismissible forms (i.e., disallowing users to back away).

Other issues left unaddressed by conventional APIs is no way to detect and handle failure when sending forms, no defined cleanup/finalization routine for navigation flows, and no shared state for nested forms (Form A→B→A). Existing libraries have incorporated specialized paginated forms to avoid boilerplate, and explicit mechanisms in nested forms to allow navigating back from child to parent form.

Approach
--------

[](#approach)

AwaitForm addresses existing issues through an alternative async/await based form-handling syntax using [await-generator](https://github.com/SOF3/await-generator).

```
$form = AwaitForm::form("Create a Ban Report", [
	FormControl::input("Player", "Enter their gamertag"),
	FormControl::dropdown("Ban Reason", ["Hacking", "Spamming", "Toxicity"]),
	FormControl::input("Comment", "Any further comments...")
]);
[$gamertag, $reason, $comment] = yield from $form->request($player);
```

 See demo[![Ban report form](https://private-user-images.githubusercontent.com/15074389/415894338-568c3775-6a2e-42d4-9383-ef559093d750.png?jwt=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3NzUxMDI0MjEsIm5iZiI6MTc3NTEwMjEyMSwicGF0aCI6Ii8xNTA3NDM4OS80MTU4OTQzMzgtNTY4YzM3NzUtNmEyZS00MmQ0LTkzODMtZWY1NTkwOTNkNzUwLnBuZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNjA0MDIlMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjYwNDAyVDAzNTUyMVomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPWI2OTcxY2RmNTM4NzY0YjljZTg2ZWUzOWFlZWQ0OTY1MGRlNzU0ZTZmYzFhNWVjMDcyODRlOTRlZWQ4YjI1ODImWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0In0.u2qIhYmRFSq-h5liryGkpjlrrPXrrh8YutKN6K2IXJo)](https://private-user-images.githubusercontent.com/15074389/415894338-568c3775-6a2e-42d4-9383-ef559093d750.png?jwt=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3NzUxMDI0MjEsIm5iZiI6MTc3NTEwMjEyMSwicGF0aCI6Ii8xNTA3NDM4OS80MTU4OTQzMzgtNTY4YzM3NzUtNmEyZS00MmQ0LTkzODMtZWY1NTkwOTNkNzUwLnBuZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNjA0MDIlMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjYwNDAyVDAzNTUyMVomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPWI2OTcxY2RmNTM4NzY0YjljZTg2ZWUzOWFlZWQ0OTY1MGRlNzU0ZTZmYzFhNWVjMDcyODRlOTRlZWQ4YjI1ODImWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0In0.u2qIhYmRFSq-h5liryGkpjlrrPXrrh8YutKN6K2IXJo)

Users get to utilize native PHP control flow syntax (while, for, continue, break, etc.; see [Retry Logic in Form](#1-retry-logic-in-form)) instead of a costly reimplementation of existing control structures which existing libraries achieve using callbacks. AwaitForm features no additional special-purpose mechanism, but still aids users in making otherwise complex [paginated](#3-paginated-button-menu) and [nested](#4-nested-forms) navigation flows.

```
// -- initialization: e.g., make player immobile when viewing form --
$player->setNoClientPredictions(true);
while(true){
	$form = AwaitForm::form("Set home here?", [FormControl::input("Home Name:")]);
	// -- request: send form and wait for response --
	try{
		[$name] = yield from $form->request($player);
	}catch(AwaitFormException){
		// -- failure: exit loop if player closes form or disconnects --
		break;
	}
	// -- evaluate: handle response --
	if(trim($name) === ""){
		$player->sendToastNotification("Invalid Name", "Home name cannot be empty");
		continue;
	}
	$player->sendMessage("Home '{$name}' set at your location!");
	break;
}
// -- finalization/cleanup: e.g., revert player movement restriction --
$player->setNoClientPredictions(false);
```

 See demo    5CN4OUB.mp4    ### When the user does not respond

[](#when-the-user-does-not-respond)

Player disconnects, server shutdowns, validation errors, and 'busy status' throw an `AwaitFormException`. Read `AwaitFormException::getCode()` to narrow down the cause to `ERR_VALIDATION_FAILED`, `ERR_PLAYER_REJECTED`, or `ERR_PLAYER_QUIT`.

```
try{
	$response = yield from $form->request($player);
}catch(AwaitFormException){
	return;
}
$player->sendMessage("Response: " . json_encode($response));
$player->sendMessage("Report Received, thank you!");
```

Example Design Models
---------------------

[](#example-design-models)

### 1. Retry logic in form

[](#1-retry-logic-in-form)

Revisiting the example above (creating a ban report), player gamertags require validation. In this example, the player is sent the form again when they enter a wrong gamertag. This design includes State Persistence whereby the user's input is not lost upon entering a wrong gamertag.

```
$gamertag = "";
$reason = null;
$comment = "";
while(true){
	$form = AwaitForm::form("Create a Ban Report", [
		FormControl::input("Player", "Enter their gamertag", $gamertag),
		FormControl::dropdown("Ban Reason", ["Hacking", "Spamming", "Toxicity"], $reason),
		FormControl::input("Comment", "Any further comments...", $comment)
	]);
	try{
		[$gamertag, $reason, $comment] = yield from $form->request($player);
	}catch(AwaitFormException){
		break;
	}
	if(!$server->hasOfflinePlayerData($gamertag)){
		$player->sendToastNotification("Player Not Found", "'{$gamertag}' never joined this server.");
		continue;
	}
	$player->sendMessage("Response: " . json_encode([$gamertag, $reason, $comment]));
	$player->sendMessage("Report Received, thank you!");
	break;
}
```

 See demo     2pj5lb2.mp4    ### 2. Non-dismissible form

[](#2-non-dismissible-form)

A player is banned and is forced to acknowledge their ban. If they close the form, the form is sent again - they cannot back away. They are also given permanent blindness until then.

```
// -- initialization: happens before main loop --
$player->getEffects()->add(new EffectInstance(VanillaEffects::BLINDNESS(), Limits::INT32_MAX));
while(true){
	$form = AwaitForm::form("You are BANNED!", [
		FormControl::toggle("I acknowledge my ban."),
		FormControl::input("Comments", "Type any comments you have...")
	]);
	try{
		[$acknowledged, $comments] = yield from $form->request($player);
	}catch(AwaitFormException $e){
		if($e->getCode() === AwaitFormException::ERR_PLAYER_QUIT){
			break;
		}
		continue;
	}
	if($acknowledged){
		echo "Comments: ", $comments, PHP_EOL;
		break;
	}
	$player->sendToastNotification("Try Again", "Acknowledgement is needed.");
}
$player->getEffects()->remove(VanillaEffects::BLINDNESS());
```

 See demo    CIXXrw9.mp4    ### 3. Paginated button menu

[](#3-paginated-button-menu)

Players can spawn combat items on a PvP server. 10 items are listed at a time in a menu. For pagination, there is a 'Previous Page' and a 'Next Page' button at the very end of the menu.

```
// -- initialization: shared state variables used across all pages --
$items = array_filter(VanillaItems::getAll(), fn($item) => $item instanceof Durable);
$offset = 0;
$length = 10;
while(true){
	$sublist = array_slice($items, $offset, $length);
	$buttons = [];
	foreach($sublist as $id => $item){
		$buttons[$id] = Button::simple($item->getName());
	}
	if($offset > 0) $buttons["prev"] = Button::simple("[Previous Page]");
	if($offset + $length < count($items)) $buttons["next"] = Button::simple("[Next Page]");
	$form = AwaitForm::menu("Free Items!", "Have fun soldier :)", $buttons);
	try{
		$response = yield from $form->request($player);
	}catch(AwaitFormException){
		break;
	}
	if($response === "prev"){
		$offset -= $length; // validation by-design: can never go negative
	}elseif($response === "next"){
		$offset += $length;
	}else{
		$item = $sublist[$response];
		$player->getInventory()->addItem($item);
	}
}
```

 See demo    o77eXSJ.mp4    ### 4. Nested forms

[](#4-nested-forms)

Revisiting the first example (creating a ban report), this change adds a confirmation form and a mechanism to store reports using a Finite State Machine.

Finite State Machines in modeling user interfaces allow you to think at a higher level of abstraction. Instead of thinking *"After player fills a ban report; the gamertag and the reason is displayed with a yes/no button to confirm filing the report"*, you think *"The UI is put in a CONFIRM state upon filing the report"* and entering the state means certain things happen.

```
$gamertag = "";
$reason = null;
$comment = "";
$state = "CREATE";
while($state !== "DESTROY"){
	if($state === "CREATE"){
		$form = AwaitForm::form("Create a Ban Report", [
			FormControl::input("Player", "Enter their gamertag", $gamertag),
			FormControl::dropdown("Ban Reason", ["Hacking", "Spamming", "Toxicity"], $reason),
			FormControl::input("Comment", "Any further comments...", $comment)
		]);
		try{
			[$gamertag, $reason, $comment] = yield from $form->request($player);
		}catch(AwaitFormException){
			$state = "DESTROY";
			continue;
		}
		if(!$server->hasOfflinePlayerData($gamertag)){
			$player->sendToastNotification("Player Not Found", "'{$gamertag}' never joined this server.");
			continue;
		}
		$state = "CONFIRM";
	}elseif($state === "CONFIRM"){
		$message = ["Are you sure you would like to file this report? Review your details:"];
		$message[] = "Gamertag: {$gamertag}";
		$message[] = "Reason: {$reason}";
		$message[] = "Comment: {$comment}";
		$form = AwaitForm::menu("Confirm Filing Report?", implode(TextFormat::EOL, $message), [
			"yes" => Button::simple("Confirm"),
			"edit" => Button::simple("Make Changes"),
			"no" => Button::simple("Cancel")
		]);
		$response = yield from $form->requestOrFallback($player, "no");
		$state = match($response){
			"yes" => "WRITE",
			"edit" => "CREATE",
			"no" => "DESTROY"
		};
	}elseif($state === "WRITE"){
		yield from $database->asyncInsert("myplugin.ban_reports", ["offender" => $gamertag, "reason" => $reason, "comment" => $comment]);
		if($player->isConnected()){
			$player->sendToastNotification("Report Successful!", "Thank you very much.");
		}
		$gamertag = "";
		$reason = null;
		$comment = "";
		$state = "CREATE";
	}
}
```

 See demo     kXntqTB.mp4    Reusing Forms
-------------

[](#reusing-forms)

Form windows store only display properties and not player state. A form window (i.e., `AwaitForm::dialog()`, `AwaitForm::form()`, `AwaitForm::menu()`) may be instantiated once and reused multiple times. All window properties that are not readonly are allowed to be mutated.

```
$form = AwaitForm::menu("title", "content", []);
$form->title = "New title";
$form->buttons[] = [Button::simple("Get free food"), "food"];
$form->buttons[] = [Button::simple("Get free block"), "block"];
while(true){
	try{
		yield from $form->request($player);
	}catch(AwaitFormException){
		break;
	}
}
```

###  Health Score

36

—

LowBetter than 82% of packages

Maintenance54

Moderate activity, may be stable

Popularity21

Limited adoption so far

Community15

Small or concentrated contributor base

Maturity45

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 80% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~76 days

Total

3

Last Release

298d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/247134f60bf8c5c7c8a2f06b0ecea431a052614283aac5093b57bde51039e34a?d=identicon)[muqsit](/maintainers/muqsit)

---

Top Contributors

[![Muqsit](https://avatars.githubusercontent.com/u/15074389?v=4)](https://github.com/Muqsit "Muqsit (8 commits)")[![DaisukeDaisuke](https://avatars.githubusercontent.com/u/17798680?v=4)](https://github.com/DaisukeDaisuke "DaisukeDaisuke (1 commits)")[![poggit-bot](https://avatars.githubusercontent.com/u/22427965?v=4)](https://github.com/poggit-bot "poggit-bot (1 commits)")

---

Tags

asyncformslibraryphppmmppocketmine-mp

### Embed Badge

![Health badge](/badges/cosmicpe-awaitform/health.svg)

```
[![Health](https://phpackages.com/badges/cosmicpe-awaitform/health.svg)](https://phpackages.com/packages/cosmicpe-awaitform)
```

###  Alternatives

[muqsit/invmenu

A PocketMine-MP virion to create and manage virtual inventories!

2234.2k1](/packages/muqsit-invmenu)[muqsit/simple-packet-handler

Handle specific data packets (virion for PMMP API 4.0.0)

426.1k3](/packages/muqsit-simple-packet-handler)[dktapps/pmforms

Form API library for PocketMine-MP plugins

522.3k1](/packages/dktapps-pmforms)[muqsit/asynciterator

A virion that simplifies writing tasks that traverse iterators

182.9k](/packages/muqsit-asynciterator)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
