Skip to content

Commit

Permalink
Add some new resources and examples
Browse files Browse the repository at this point in the history
  • Loading branch information
inxilpro committed Jan 6, 2025
1 parent 28c9bc1 commit 4c90c36
Show file tree
Hide file tree
Showing 14 changed files with 558 additions and 21 deletions.
3 changes: 3 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@
"autoload-dev": {
"psr-4": {
"Thunk\\Verbs\\Tests\\": "tests/",
"Thunk\\Verbs\\Examples\\Cart\\": "examples/Cart/src/",
"Thunk\\Verbs\\Examples\\Cart\\Tests\\": "examples/Cart/tests/",
"Thunk\\Verbs\\Examples\\Cart\\Database\\Factories\\": "examples/Cart/database/factories/",
"Thunk\\Verbs\\Examples\\Counter\\": "examples/Counter/src/",
"Thunk\\Verbs\\Examples\\Counter\\Tests\\": "examples/Counter/tests/",
"Thunk\\Verbs\\Examples\\Counter\\Database\\Factories\\": "examples/Counter/database/factories/",
Expand Down
6 changes: 6 additions & 0 deletions docs/articles.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
This is a collection of articles and blog posts that have been published about Verbs. If you have written something
about Verbs, feel free to [submit a PR](https://github.com/hirethunk/verbs/blob/main/docs/articles.md) to add it
to the list.

- [Models in Verbs](https://cmorrell.com/models-in-verbs) (Chris Morrell - 2024)
- [Verbs Validation Errors](https://cmorrell.com/verbs-errors) (Chris Morrell - 2024)
43 changes: 22 additions & 21 deletions docs/navigation.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,32 @@
}
]
},
{
"title": "Resources",
"slug": "resources",
"items": [
{
"title": "Videos",
"slug": "videos",
"icon": "tv"
},
{
"title": "Articles",
"slug": "articles",
"icon": "newspaper"
}
]
},
{
"title": "Examples",
"slug": "examples",
"items": [
{
"title": "Shopping Cart",
"slug": "cart",
"icon": "shopping-cart",
"url": "/examples/cart"
},
{
"title": "Board Game",
"slug": "monopoly",
Expand Down Expand Up @@ -105,26 +127,5 @@
"icon": "circle-stack"
}
]
},
{
"title": "Packages",
"slug": "verbs-packages",
"items": [
{
"title": "Verbs Commands",
"slug": "verbs-commands",
"icon": "window"
},
{
"title": "Verbs History",
"slug": "verbs-history",
"icon": "book-open"
},
{
"title": "Verbs Livewire",
"slug": "verbs-livewire",
"icon": "bolt"
}
]
}
]
12 changes: 12 additions & 0 deletions docs/videos.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
> [!note]
> These videos are relatively off-the-cuff and are using Verbs pre-releases. Some features
> may change, or best practices may emerge prior to version 1.0 that will make these videos
> obsolete. Once Verbs 1.0 is released, we plan on publishing a full, produced video series
> on Verbs.
-------

Chris has posted a couple of introductory videos to YouTube. You can watch the playlist
below, or [view it on YouTube](https://www.youtube.com/playlist?list=PLfp8k2-5wAK5Knn7HT43k1MJca08q_1nP).

https://youtube.com/playlist?list=PLfp8k2-5wAK5Knn7HT43k1MJca08q_1nP&si=8wbEtU4Sz6Qp3A5_
4 changes: 4 additions & 0 deletions examples/Cart/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Shopping Cart Example

This example is the code written in Chris's [Using state in Verbs](https://www.youtube.com/watch?v=d6_ggotpb_8&t=594s)
YouTube video. Tests and a few comments have been added.
55 changes: 55 additions & 0 deletions examples/Cart/navigation.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
[
{
"title": "Intro",
"slug": "intro",
"items": [
{
"title": "Shopping Cart Example",
"slug": "readme",
"path": "README.md"
}
]
},
{
"title": "Events",
"slug": "events",
"items": [
{
"title": "ItemRestocked",
"slug": "restock",
"path": "src/Events/ItemRestocked.php"
},
{
"title": "ItemAddedToCart",
"slug": "add-to-cart",
"path": "src/Events/ItemAddedToCart.php"
},
{
"title": "ItemRemovedFromCart",
"slug": "remove-from-cart",
"path": "src/Events/ItemRemovedFromCart.php"
},
{
"title": "CheckedOut",
"slug": "checkout",
"path": "src/Events/CheckedOut.php"
}
]
},
{
"title": "State",
"slug": "state",
"items": [
{
"title": "CartState",
"slug": "cart",
"path": "src/States/CartState.php"
},
{
"title": "ItemState",
"slug": "item",
"path": "src/States/ItemState.php"
}
]
}
]
96 changes: 96 additions & 0 deletions examples/Cart/src/Console/Commands/ShopCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
<?php

namespace Thunk\Verbs\Examples\Cart\Console\Commands;

use Closure;
use Exception;
use Illuminate\Console\Command;
use Thunk\Verbs\Examples\Cart\Events\CheckedOut;
use Thunk\Verbs\Examples\Cart\Events\ItemAddedToCart;
use Thunk\Verbs\Examples\Cart\Events\ItemRemovedFromCart;
use Thunk\Verbs\Examples\Cart\Events\ItemRestocked;

use function Laravel\Prompts\error;
use function Laravel\Prompts\select;
use function Laravel\Prompts\text;

class ShopCommand extends Command
{
protected $signature = 'shop';

protected int $cart_id;

protected array $stickers;

public function handle()
{
$this->setup();

do {
try {
$action = $this->action();
$this->getLaravel()->forgetScopedInstances(); // Emulate a new request
$action();
} catch (Exception $exception) {
error("Error: {$exception->getMessage()}");
}
} while (true);
}

protected function action(): Closure
{
$selection = select(
label: 'What would you like to do?',
options: [
'Add item to cart',
'Remove item from cart',
'Check out',
'Restock items',
]
);

return match ($selection) {
'Add item to cart' => function () {
[$item, $quantity] = $this->selectSticker();
ItemAddedToCart::commit(cart: $this->cart_id, item: $item, quantity: $quantity);
},
'Remove item from cart' => function () {
[$item, $quantity] = $this->selectSticker();
ItemRemovedFromCart::commit(cart: $this->cart_id, item: $item, quantity: $quantity);
},
'Check out' => function () {
CheckedOut::commit(cart: $this->cart_id);
},
'Restock items' => function () {
[$sticker, $quantity] = $this->selectSticker(4);
ItemRestocked::commit(item: $sticker, quantity: (int) $quantity);
},
};
}

protected function selectSticker($default_quantity = 1): array
{
$sticker = select('Which sticker?', $this->stickers);
$quantity = (int) text('Quantity', default: $default_quantity, required: true, validate: ['quantity' => 'required|int|min:1']);

return [$sticker, $quantity];
}

protected function setup()
{
// Each time we load our app we'll create a new shopping cart by assigning
// it a unique ID.

$this->cart_id = snowflake_id();

// These are entirely arbitrary IDs. They're just hard-coded so that they're
// consistent across separate runs of the app.

$this->stickers = [
1000 => 'PHP×Philly',
1001 => 'Ignore Prev. Instructions',
1002 => 'Verbs',
1003 => 'Over Engineered',
];
}
}
34 changes: 34 additions & 0 deletions examples/Cart/src/Events/CheckedOut.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php

namespace Thunk\Verbs\Examples\Cart\Events;

use Thunk\Verbs\Event;
use Thunk\Verbs\Examples\Cart\States\CartState;
use Thunk\Verbs\Examples\Cart\States\ItemState;

class CheckedOut extends Event
{
public CartState $cart;

public function validate()
{
$this->assert(! $this->cart->checked_out, 'Already checked out');

foreach ($this->cart->items as $item_id => $quantity) {
$item = ItemState::load($item_id);
$held = $item->activeHolds()[$this->cart->id]['quantity'] ?? 0;
$this->assert($held + $item->available() >= $quantity, 'Some items in your cart are out of stock');
}
}

public function apply()
{
foreach ($this->cart->items as $item_id => $quantity) {
$item = ItemState::load($item_id);
$item->quantity -= $quantity;
unset($item->holds[$this->cart->id]);
}

$this->cart->checked_out = true;
}
}
50 changes: 50 additions & 0 deletions examples/Cart/src/Events/ItemAddedToCart.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<?php

namespace Thunk\Verbs\Examples\Cart\Events;

use Thunk\Verbs\Event;
use Thunk\Verbs\Examples\Cart\States\CartState;
use Thunk\Verbs\Examples\Cart\States\ItemState;

class ItemAddedToCart extends Event
{
public static int $item_limit = 2;

public static int $hold_seconds = 5;

public ItemState $item;

public CartState $cart;

public int $quantity;

public function validate()
{
$this->assert(! $this->cart->checked_out, 'Already checked out');

$this->assert(
$this->item->available() >= $this->quantity,
'Out of stock',
);

$this->assert(
$this->cart->count($this->item->id) + $this->quantity <= self::$item_limit,
'Reached limit'
);
}

public function apply()
{
// Add (additional?) quantity to cart
$this->cart->items[$this->item->id] = $this->cart->count($this->item->id) + $this->quantity;

// Initialize hold to 0 if it doesn't already exist
$this->item->holds[$this->cart->id] ??= [
'quantity' => 0,
'expires' => now()->unix() + self::$hold_seconds,
];

// Add quantity to hold
$this->item->holds[$this->cart->id]['quantity'] += $this->quantity;
}
}
35 changes: 35 additions & 0 deletions examples/Cart/src/Events/ItemRemovedFromCart.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?php

namespace Thunk\Verbs\Examples\Cart\Events;

use Thunk\Verbs\Event;
use Thunk\Verbs\Examples\Cart\States\CartState;
use Thunk\Verbs\Examples\Cart\States\ItemState;

class ItemRemovedFromCart extends Event
{
public CartState $cart;

public ItemState $item;

public int $quantity;

public function validate()
{
$this->assert(! $this->cart->checked_out, 'Already checked out');

$this->assert(
$this->cart->count($this->item->id) >= $this->quantity,
"There aren't {$this->quantity} items in the cart to remove."
);
}

public function apply()
{
$this->cart->items[$this->item->id] -= $this->quantity;

if (isset($this->item->holds[$this->cart->id])) {
$this->item->holds[$this->cart->id]['quantity'] -= $this->quantity;
}
}
}
18 changes: 18 additions & 0 deletions examples/Cart/src/Events/ItemRestocked.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

namespace Thunk\Verbs\Examples\Cart\Events;

use Thunk\Verbs\Event;
use Thunk\Verbs\Examples\Cart\States\ItemState;

class ItemRestocked extends Event
{
public ItemState $item;

public int $quantity;

public function apply()
{
$this->item->quantity += $this->quantity;
}
}
Loading

0 comments on commit 4c90c36

Please sign in to comment.