Skip to content

Commit fc40394

Browse files
authored
Merge pull request #8 from theokeist/codex/update-documentation-with-types-and-arguments-uey0pt
Add colored CLI help output
2 parents 1211e58 + 05b7a6a commit fc40394

7 files changed

Lines changed: 203 additions & 8 deletions

File tree

docs/CLI.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,24 @@ php pipeflow make:channel <Name>
203203

204204
---
205205

206+
### `make:service`
207+
208+
**Purpose:** Generate a service class.
209+
210+
**Usage:**
211+
```bash
212+
php pipeflow make:service <Name>
213+
```
214+
215+
**Output:**
216+
- Writes `app/Services/<Name>Service.php`.
217+
218+
**Related classes:**
219+
- `PipeFlow\Console\Commands\MakeServiceCommand`
220+
- [`PipeFlow\Scaffold\Generator`](reference/src__Scaffold__Generator.php.md)
221+
222+
---
223+
206224
### `make:test`
207225

208226
**Purpose:** Generate a PHPUnit test class.

docs/GETTING_STARTED.md

Lines changed: 49 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -498,7 +498,7 @@ This is useful for dashboard widgets or front-end frameworks.
498498

499499
For more complex apps, move logic into a service class:
500500

501-
Create `app/Services/PostPublisher.php`:
501+
Create `app/Services/PostPublisherService.php`:
502502

503503
```php
504504
<?php
@@ -509,7 +509,7 @@ namespace App\Services;
509509

510510
use App\Models\Post;
511511

512-
final class PostPublisher
512+
final class PostPublisherService
513513
{
514514
/** @param array<string, mixed> $data */
515515
public function publish(array $data): Post
@@ -520,10 +520,18 @@ final class PostPublisher
520520
}
521521
```
522522

523+
You can generate a service class with:
524+
525+
```bash
526+
php pipeflow make:service PostPublisher
527+
```
528+
529+
This creates `app/Services/PostPublisherService.php`.
530+
523531
Then inject it in your controller:
524532

525533
```php
526-
public function store(Request $request, PostPublisher $publisher, Conn $conn): Response
534+
public function store(Request $request, PostPublisherService $publisher, Conn $conn): Response
527535
{
528536
$data = $request->only(['title', 'body']);
529537
$errors = $this->validate($data);
@@ -541,6 +549,32 @@ public function store(Request $request, PostPublisher $publisher, Conn $conn): R
541549
}
542550
```
543551

552+
### When to use services (external APIs, AI, cloud)
553+
554+
Services are the right place to integrate with third‑party APIs (payment, email, cloud storage, AI). They should:
555+
556+
- Accept plain data in method arguments (avoid storing request-specific state).
557+
- Wrap external SDK clients (Stripe, OpenAI, S3) behind a tiny interface.
558+
- Return plain PHP arrays or value objects so controllers remain thin.
559+
560+
Example skeleton for an external API service:
561+
562+
```php
563+
namespace App\Services;
564+
565+
final class AiSummaryService
566+
{
567+
public function summarize(string $text): string
568+
{
569+
// Call a 3rd-party SDK or HTTP client here.
570+
// Return a string so controllers/views stay simple.
571+
return $text;
572+
}
573+
}
574+
```
575+
576+
If you need framework config, read it inside the method (or inject via constructor) and keep the service stateless.
577+
544578
## 6.10.1) Public vs private helpers in controllers
545579

546580
Keep controller actions `public` and push shared logic into `private` helpers:
@@ -782,6 +816,18 @@ php pipeflow route:list
782816

783817
You’ll see all route patterns, names, and target controllers in a table.
784818

819+
Example output (indented columns):
820+
821+
```
822+
GET /posts posts.index
823+
GET /posts/create posts.create
824+
POST /posts posts.store
825+
GET /posts/{id} posts.show
826+
GET /posts/{id}/edit posts.edit
827+
POST /posts/{id} posts.update
828+
POST /posts/{id}/delete posts.destroy
829+
```
830+
785831
## 8) Add flash feedback (optional)
786832

787833
PipeFlow ships with a flash system so you can show one-time notices after redirects.

docs/WORKERS.md

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,16 @@ $kernel = new PipeFlow\Foundation\Kernel($app);
3030
- Use the request-scoped container (`Application::newRequestContainer()`) for
3131
anything that is request-specific.
3232
- Avoid static caches that capture user-specific data.
33+
- **Clear view/shared state** if you push globals or shared view data.
34+
- **Reset per-request timers/metrics** in `beforeRequest` hooks.
35+
- **Avoid global mutable config** unless it is read-only.
36+
37+
## Typical leak sources (checklist)
38+
39+
- Static properties on services holding the last request/user.
40+
- Global arrays like `$_SESSION` or `$GLOBALS['__pipeflow_view_data']` reused across requests.
41+
- Cached QueryBuilder instances shared across requests.
42+
- Random seeds or locale/timezone mutations not reset.
3343

3444
## Per-request cleanup hooks
3545

@@ -49,3 +59,38 @@ $kernel->afterRequest(function ($request, $response, $container) {
4959

5060
For long-lived workers, call `Connection::reconnect()` if a connection becomes
5161
stale or times out.
62+
63+
## Worker-safe patterns
64+
65+
- Instantiate services once, but keep request data in method scope.
66+
- Prefer passing data into methods rather than storing it on the instance.
67+
- Use the `Conn` assigns for per-request values you need in views.
68+
- For SDK clients (cloud, AI, payment), reuse the client but **do not** cache per-request tokens on the service.
69+
70+
## Services + workers (external APIs)
71+
72+
When running RoadRunner/Swoole, services that call external APIs should be:
73+
74+
- **Stateless:** no request-specific fields stored on the service.
75+
- **Client-reusable:** keep a shared SDK/HTTP client and pass per-request auth or payloads as arguments.
76+
- **Timeout-aware:** external calls can hang; configure timeouts on the HTTP client.
77+
- **Retry-safe:** if the SDK retries, make sure the operation is idempotent or guarded.
78+
79+
Example pattern:
80+
81+
```php
82+
final class CloudStorageService
83+
{
84+
public function __construct(private readonly HttpClient $client) {}
85+
86+
public function upload(string $path, string $contents, string $token): void
87+
{
88+
$this->client->post('/upload', [
89+
'headers' => ['Authorization' => "Bearer {$token}"],
90+
'body' => $contents,
91+
]);
92+
}
93+
}
94+
```
95+
96+
This keeps the client reusable while all request-specific values are passed in.

pipeflow

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ use PipeFlow\Console\Commands\MakeChannelCommand;
1313
use PipeFlow\Console\Commands\MakeTestCommand;
1414
use PipeFlow\Console\Commands\MakeFactoryCommand;
1515
use PipeFlow\Console\Commands\MakeFixtureCommand;
16+
use PipeFlow\Console\Commands\MakeServiceCommand;
1617
use PipeFlow\Console\Commands\MigrateCommand;
1718
use PipeFlow\Console\Commands\MigrateRollbackCommand;
1819
use PipeFlow\Console\Commands\RouteListCommand;
@@ -30,6 +31,7 @@ $cli->register(new MakeChannelCommand());
3031
$cli->register(new MakeTestCommand());
3132
$cli->register(new MakeFactoryCommand());
3233
$cli->register(new MakeFixtureCommand());
34+
$cli->register(new MakeServiceCommand());
3335
$cli->register(new MigrateCommand());
3436
$cli->register(new MigrateRollbackCommand());
3537
$cli->register(new RouteListCommand());
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace PipeFlow\Console\Commands;
6+
7+
use PipeFlow\Console\CommandInterface;
8+
use PipeFlow\Foundation\Application;
9+
use PipeFlow\Scaffold\Generator;
10+
11+
final class MakeServiceCommand implements CommandInterface
12+
{
13+
public function name(): string { return 'make:service'; }
14+
public function description(): string { return 'Generate a service class.'; }
15+
16+
public function handle(array $args, Application $app): int
17+
{
18+
$name = $args[0] ?? null;
19+
if (!$name) {
20+
fwrite(STDERR, "Usage: make:service <Name>\n");
21+
return 2;
22+
}
23+
24+
$gen = new Generator($app);
25+
$class = preg_replace('/Service$/', '', $name) . 'Service';
26+
$namespace = 'App\\Services';
27+
$path = 'app/Services/' . $class . '.php';
28+
29+
$stub = $gen->stub('service.php.stub');
30+
$contents = str_replace(
31+
['{{namespace}}', '{{class}}'],
32+
[$namespace, $class],
33+
$stub
34+
);
35+
36+
$gen->write($path, $contents);
37+
echo "Created: {$path}\n";
38+
return 0;
39+
}
40+
}

src/Console/ConsoleApplication.php

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ public function run(array $argv): int
3030
}
3131

3232
if (!isset($this->commands[$name])) {
33-
fwrite(STDERR, "Unknown command: {$name}\n\n");
33+
fwrite(STDERR, $this->colorize("Unknown command: {$name}\n\n", 'red'));
3434
$this->printHelp();
3535
return 2;
3636
}
@@ -41,9 +41,9 @@ public function run(array $argv): int
4141

4242
private function printHelp(): void
4343
{
44-
echo "PipeFlow CLI\n";
45-
echo "Usage: php pipeflow <command> [options]\n\n";
46-
echo "Commands:\n";
44+
echo $this->colorize("PipeFlow CLI\n", 'bold_cyan');
45+
echo $this->colorize("Usage: php pipeflow <command> [options]\n\n", 'cyan');
46+
echo $this->colorize("Commands:\n", 'bold');
4747

4848
$max = 0;
4949
foreach ($this->commands as $c) {
@@ -52,9 +52,41 @@ private function printHelp(): void
5252

5353
foreach ($this->commands as $c) {
5454
$pad = str_repeat(' ', $max - strlen($c->name()) + 2);
55-
echo " {$c->name()}{$pad}{$c->description()}\n";
55+
$name = $this->colorize($c->name(), 'green');
56+
echo " {$name}{$pad}{$c->description()}\n";
5657
}
5758

5859
echo "\n";
5960
}
61+
62+
private function colorize(string $text, string $style): string
63+
{
64+
if (!$this->supportsColor()) {
65+
return $text;
66+
}
67+
68+
$codes = [
69+
'bold' => '1',
70+
'red' => '31',
71+
'green' => '32',
72+
'cyan' => '36',
73+
'bold_cyan' => '1;36',
74+
];
75+
76+
$code = $codes[$style] ?? null;
77+
if ($code === null) {
78+
return $text;
79+
}
80+
81+
return "\033[{$code}m{$text}\033[0m";
82+
}
83+
84+
private function supportsColor(): bool
85+
{
86+
if (!function_exists('posix_isatty')) {
87+
return false;
88+
}
89+
90+
return posix_isatty(STDOUT) || posix_isatty(STDERR);
91+
}
6092
}

stubs/service.php.stub

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace {{namespace}};
6+
7+
final class {{class}}
8+
{
9+
public function __construct()
10+
{
11+
}
12+
}

0 commit comments

Comments
 (0)