@baldurpan/create-ai-workflow 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +165 -0
- package/dist/agents-block.js +70 -0
- package/dist/bin.js +14 -0
- package/dist/check/markdown.js +47 -0
- package/dist/check/parse.js +113 -0
- package/dist/check/rules.js +245 -0
- package/dist/cli.js +109 -0
- package/dist/commands/check.js +41 -0
- package/dist/commands/install.js +76 -0
- package/dist/commands/standards-add.js +145 -0
- package/dist/commands/update.js +214 -0
- package/dist/layout.js +115 -0
- package/dist/log.js +20 -0
- package/dist/manifest.js +53 -0
- package/dist/paths.js +41 -0
- package/package.json +37 -0
- package/templates/blocks/agents-block.md +30 -0
- package/templates/claude/agents/planner.agent.md +60 -0
- package/templates/claude/agents/reviewer.agent.md +77 -0
- package/templates/context/README.md +81 -0
- package/templates/context/plan-template.md +69 -0
- package/templates/context/plan-template.notes.md +55 -0
- package/templates/context/roles/coder.md +76 -0
- package/templates/context/workflow.md +143 -0
- package/templates/skills/feature-close/SKILL.md +102 -0
- package/templates/skills/feature-implement/SKILL.md +142 -0
- package/templates/skills/feature-plan/SKILL.md +138 -0
- package/templates/skills/feature-status/SKILL.md +86 -0
- package/templates/skills/onboard/SKILL.md +122 -0
- package/templates/skills/orchestrate/SKILL.md +92 -0
- package/templates/skills/roadmap/SKILL.md +97 -0
- package/templates/standards/.source +8 -0
- package/templates/standards/README.md +104 -0
- package/templates/standards/architecture/api-design.md +212 -0
- package/templates/standards/architecture/dependency-boundaries.md +104 -0
- package/templates/standards/architecture/feature-driven.md +102 -0
- package/templates/standards/architecture/folder-structure.md +108 -0
- package/templates/standards/architecture/monorepos.md +107 -0
- package/templates/standards/architecture/refactoring.md +97 -0
- package/templates/standards/architecture/shared-code.md +81 -0
- package/templates/standards/docs/PHP-SPEC.md +1195 -0
- package/templates/standards/docs/SPEC.md +540 -0
- package/templates/standards/examples/bad/data-fetching.tsx +52 -0
- package/templates/standards/examples/bad/derived-state.tsx +60 -0
- package/templates/standards/examples/bad/feature-component.tsx +80 -0
- package/templates/standards/examples/bad/prop-drilling.tsx +70 -0
- package/templates/standards/examples/good/data-fetching.tsx +53 -0
- package/templates/standards/examples/good/derived-state.tsx +52 -0
- package/templates/standards/examples/good/feature-component.tsx +141 -0
- package/templates/standards/examples/good/invoice.schema.ts +20 -0
- package/templates/standards/examples/good/prop-drilling.tsx +83 -0
- package/templates/standards/philosophy/ai-agent-behavior.md +93 -0
- package/templates/standards/philosophy/core-principles.md +49 -0
- package/templates/standards/philosophy/incremental-abstraction.md +56 -0
- package/templates/standards/philosophy/maintainability.md +66 -0
- package/templates/standards/philosophy/readability.md +87 -0
- package/templates/standards/php/README.md +62 -0
- package/templates/standards/php/anti-patterns.md +259 -0
- package/templates/standards/php/architecture.md +241 -0
- package/templates/standards/php/frameworks.md +197 -0
- package/templates/standards/php/rules.md +205 -0
- package/templates/standards/php/security.md +190 -0
- package/templates/standards/php/testing.md +139 -0
- package/templates/standards/php/tooling.md +245 -0
- package/templates/standards/react/accessibility.md +130 -0
- package/templates/standards/react/anti-patterns.md +187 -0
- package/templates/standards/react/component-design.md +139 -0
- package/templates/standards/react/error-boundaries.md +195 -0
- package/templates/standards/react/forms.md +147 -0
- package/templates/standards/react/hooks.md +88 -0
- package/templates/standards/react/memoization.md +87 -0
- package/templates/standards/react/state-management.md +123 -0
- package/templates/standards/react/testing.md +118 -0
- package/templates/standards/react/use-effect.md +110 -0
- package/templates/standards/security/api-security.md +136 -0
- package/templates/standards/security/auth.md +112 -0
- package/templates/standards/security/secrets.md +105 -0
- package/templates/standards/security/validation.md +89 -0
- package/templates/standards/templates/.editorconfig +16 -0
- package/templates/standards/templates/.nvmrc +1 -0
- package/templates/standards/templates/AGENTS.md +46 -0
- package/templates/standards/templates/CLAUDE.md +3 -0
- package/templates/standards/templates/_dot_gitignore +50 -0
- package/templates/standards/templates/biome.json +53 -0
- package/templates/standards/templates/eslint.config.js +61 -0
- package/templates/standards/templates/project-readme.md +76 -0
- package/templates/standards/templates/pull-request-template.md +46 -0
- package/templates/standards/templates/tsconfig.json +35 -0
- package/templates/standards/tooling/biome.md +92 -0
- package/templates/standards/tooling/ci.md +128 -0
- package/templates/standards/tooling/dates.md +194 -0
- package/templates/standards/tooling/dependencies.md +90 -0
- package/templates/standards/tooling/nx.md +113 -0
- package/templates/standards/tooling/observability.md +183 -0
- package/templates/standards/tooling/prisma.md +152 -0
- package/templates/standards/tooling/shadcn.md +115 -0
- package/templates/standards/tooling/tailwind.md +135 -0
- package/templates/standards/tooling/tanstack-query.md +214 -0
- package/templates/standards/tooling/tanstack-router.md +365 -0
- package/templates/standards/tooling/tanstack.md +31 -0
- package/templates/standards/tooling/vite.md +135 -0
- package/templates/standards/typescript/anti-patterns.md +107 -0
- package/templates/standards/typescript/error-handling.md +216 -0
- package/templates/standards/typescript/naming.md +78 -0
- package/templates/standards/typescript/rules.md +159 -0
- package/templates/standards/typescript/tsconfig/base.json +32 -0
- package/templates/standards/typescript/tsconfig/nextjs.json +12 -0
- package/templates/standards/typescript/tsconfig/react.json +9 -0
- package/templates/standards/typescript/validation.md +104 -0
- package/templates/stubs/executors.md +49 -0
- package/templates/stubs/findings.md +40 -0
- package/templates/stubs/history.md +13 -0
- package/templates/stubs/roadmap.md +29 -0
- package/templates/stubs/stack.md +35 -0
- package/templates/stubs/verify.md +48 -0
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# Readability
|
|
2
|
+
|
|
3
|
+
## Core Rule
|
|
4
|
+
|
|
5
|
+
Code is read far more than it is written. Optimize for the reader.
|
|
6
|
+
|
|
7
|
+
## DO
|
|
8
|
+
|
|
9
|
+
- Write short, focused files with a single primary responsibility
|
|
10
|
+
- Prefer flat over deeply nested logic
|
|
11
|
+
- Use early returns to reduce nesting
|
|
12
|
+
- Keep function bodies short — if it doesn't fit on one screen, consider splitting
|
|
13
|
+
- Express intent in names, not comments
|
|
14
|
+
- Make control flow obvious
|
|
15
|
+
|
|
16
|
+
## DO NOT
|
|
17
|
+
|
|
18
|
+
- Write comments that explain what the code does (use a better name instead)
|
|
19
|
+
- Chain more than 3–4 operations without an intermediate named variable
|
|
20
|
+
- Use ternaries for non-trivial conditions
|
|
21
|
+
- Nest ternaries
|
|
22
|
+
- Write clever one-liners that require mental parsing
|
|
23
|
+
|
|
24
|
+
## Comments
|
|
25
|
+
|
|
26
|
+
Write a comment only when the **why** is non-obvious:
|
|
27
|
+
- A hidden constraint or business rule
|
|
28
|
+
- A workaround for a specific bug or browser quirk
|
|
29
|
+
- A subtle invariant that would surprise a reader
|
|
30
|
+
|
|
31
|
+
Do not write comments that describe what the code does. The code already says that.
|
|
32
|
+
|
|
33
|
+
```ts
|
|
34
|
+
// BAD: explains what
|
|
35
|
+
// increment the counter
|
|
36
|
+
count += 1;
|
|
37
|
+
|
|
38
|
+
// GOOD: explains why
|
|
39
|
+
// Safari fires the resize event on scroll — debounce to avoid layout thrash
|
|
40
|
+
window.addEventListener("resize", debouncedResize);
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Control Flow
|
|
44
|
+
|
|
45
|
+
Prefer:
|
|
46
|
+
|
|
47
|
+
```ts
|
|
48
|
+
// early return over nested if
|
|
49
|
+
if (!user) return null;
|
|
50
|
+
if (!user.isActive) return <Suspended />;
|
|
51
|
+
|
|
52
|
+
return <Dashboard user={user} />;
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Over:
|
|
56
|
+
|
|
57
|
+
```ts
|
|
58
|
+
if (user) {
|
|
59
|
+
if (user.isActive) {
|
|
60
|
+
return <Dashboard user={user} />;
|
|
61
|
+
} else {
|
|
62
|
+
return <Suspended />;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return null;
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Naming
|
|
69
|
+
|
|
70
|
+
Names should make a reader's job easier:
|
|
71
|
+
|
|
72
|
+
- `isLoading` not `loading` (boolean prefix)
|
|
73
|
+
- `formatCurrency` not `format` (specific action)
|
|
74
|
+
- `selectedUserId` not `id` (entity + role)
|
|
75
|
+
- `InvoiceLineItemRow` not `Row` (full context)
|
|
76
|
+
|
|
77
|
+
## PRIORITY
|
|
78
|
+
|
|
79
|
+
```
|
|
80
|
+
Obviousness > Brevity > Cleverness
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## See Also
|
|
84
|
+
|
|
85
|
+
- [`maintainability.md`](maintainability.md) — colocation, deletability
|
|
86
|
+
- [`../typescript/naming.md`](../typescript/naming.md) — naming conventions
|
|
87
|
+
- [`../react/component-design.md`](../react/component-design.md) — file size and decomposition signals
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# PHP Engineering Standards
|
|
2
|
+
|
|
3
|
+
> **When to load these standards:** Only when the task is explicitly PHP. This repository's primary stack is TypeScript on Node.js. PHP is supported as a secondary track for the rare case where it's the right tool — typically internal tooling, CMS integrations, queue-driven workflows, or PHP-first ecosystems (Laravel/Symfony/Slim projects).
|
|
4
|
+
>
|
|
5
|
+
> **Do not suggest PHP** for a new project, a Node-friendly task, or a TypeScript codebase. If the user wants PHP, they will say so explicitly.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Philosophy
|
|
10
|
+
|
|
11
|
+
Modern PHP is not "sprinkle PHP into HTML templates." A modern PHP codebase is:
|
|
12
|
+
|
|
13
|
+
- Strongly typed wherever possible
|
|
14
|
+
- Layered with clear domain boundaries
|
|
15
|
+
- Framework-agnostic at the domain layer
|
|
16
|
+
- Built around dependency injection
|
|
17
|
+
- Free of global state and magic-heavy abstractions
|
|
18
|
+
- Testable by design
|
|
19
|
+
- Explicit over convenient
|
|
20
|
+
|
|
21
|
+
Treat PHP like C#, Kotlin, Java, or TypeScript backends — structured, typed, layered, observable.
|
|
22
|
+
|
|
23
|
+
## Baseline for New PHP Projects
|
|
24
|
+
|
|
25
|
+
- PHP 8.3+
|
|
26
|
+
- `declare(strict_types=1);` in every file
|
|
27
|
+
- PHPStan at level max
|
|
28
|
+
- Pint or PHP-CS-Fixer for formatting
|
|
29
|
+
- Pest or PHPUnit for tests
|
|
30
|
+
- Dockerized dev environment
|
|
31
|
+
- PostgreSQL (MySQL acceptable)
|
|
32
|
+
- Redis (for cache + queues)
|
|
33
|
+
- Structured logging
|
|
34
|
+
- CI/CD from day one
|
|
35
|
+
|
|
36
|
+
## Conditional Loading
|
|
37
|
+
|
|
38
|
+
| If the task involves… | Load… |
|
|
39
|
+
|---|---|
|
|
40
|
+
| Any PHP task | [`rules.md`](rules.md), [`anti-patterns.md`](anti-patterns.md) |
|
|
41
|
+
| Architecture / new module | [`architecture.md`](architecture.md) |
|
|
42
|
+
| Testing | [`testing.md`](testing.md) |
|
|
43
|
+
| Security | [`security.md`](security.md) |
|
|
44
|
+
| Tooling setup (Composer, PHPStan, Pint, Pest, Rector) | [`tooling.md`](tooling.md) |
|
|
45
|
+
| Framework choice or framework-specific patterns | [`frameworks.md`](frameworks.md) |
|
|
46
|
+
|
|
47
|
+
## Cross-Cutting Topics (Reuse Main Repo)
|
|
48
|
+
|
|
49
|
+
These concepts apply across languages — use the main repo's docs:
|
|
50
|
+
|
|
51
|
+
| Topic | Doc |
|
|
52
|
+
|---|---|
|
|
53
|
+
| Logging, error monitoring, observability | [`../tooling/observability.md`](../tooling/observability.md) |
|
|
54
|
+
| CI gating, PR sizing, squash merges | [`../tooling/ci.md`](../tooling/ci.md) |
|
|
55
|
+
| API design (REST, errors, pagination) | [`../architecture/api-design.md`](../architecture/api-design.md) |
|
|
56
|
+
| Refactoring discipline | [`../architecture/refactoring.md`](../architecture/refactoring.md) |
|
|
57
|
+
| Date/time handling | [`../tooling/dates.md`](../tooling/dates.md) — use the PHP equivalent (`DateTimeImmutable`, Carbon, or Brick\DateTime) |
|
|
58
|
+
| Dependency boundaries (in a modular monolith) | [`../architecture/dependency-boundaries.md`](../architecture/dependency-boundaries.md) |
|
|
59
|
+
|
|
60
|
+
## Documentation Style
|
|
61
|
+
|
|
62
|
+
Same as the rest of this repo: directive, concise, hierarchical, example-heavy. `## DO` / `## DO NOT` / `## PRIORITY` sections. No prose essays.
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
# PHP Anti-Patterns
|
|
2
|
+
|
|
3
|
+
## God Arrays
|
|
4
|
+
|
|
5
|
+
```php
|
|
6
|
+
// BAD — array as data contract; no type safety, no IDE help
|
|
7
|
+
function processUser(array $user): void
|
|
8
|
+
{
|
|
9
|
+
$email = $user['email']; // typo risk, missing key risk
|
|
10
|
+
$name = $user['profile']['name']; // deep array access
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// GOOD — DTO or value object
|
|
14
|
+
function processUser(User $user): void
|
|
15
|
+
{
|
|
16
|
+
$email = $user->email;
|
|
17
|
+
$name = $user->profile->name;
|
|
18
|
+
}
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Static Singleton State
|
|
22
|
+
|
|
23
|
+
```php
|
|
24
|
+
// BAD — hidden global state, untestable
|
|
25
|
+
class Settings
|
|
26
|
+
{
|
|
27
|
+
private static array $values = [];
|
|
28
|
+
|
|
29
|
+
public static function get(string $key): mixed
|
|
30
|
+
{
|
|
31
|
+
return self::$values[$key] ?? null;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
Settings::get('feature_flag.x');
|
|
36
|
+
|
|
37
|
+
// GOOD — injected dependency
|
|
38
|
+
final class SettingsService
|
|
39
|
+
{
|
|
40
|
+
public function __construct(private readonly SettingsRepository $repo) {}
|
|
41
|
+
|
|
42
|
+
public function get(string $key): mixed
|
|
43
|
+
{
|
|
44
|
+
return $this->repo->find($key);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Service Locator / Facade Abuse in Business Code
|
|
50
|
+
|
|
51
|
+
```php
|
|
52
|
+
// BAD — hidden dependencies, harder to test, magic
|
|
53
|
+
class OrderService
|
|
54
|
+
{
|
|
55
|
+
public function process(Order $order): void
|
|
56
|
+
{
|
|
57
|
+
$payment = app(PaymentGateway::class);
|
|
58
|
+
$logger = resolve(Logger::class);
|
|
59
|
+
DB::transaction(function () use ($order) { /* ... */ });
|
|
60
|
+
Cache::forget("order:{$order->id}");
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// GOOD — constructor injection
|
|
65
|
+
final class OrderService
|
|
66
|
+
{
|
|
67
|
+
public function __construct(
|
|
68
|
+
private readonly PaymentGateway $payments,
|
|
69
|
+
private readonly LoggerInterface $logger,
|
|
70
|
+
private readonly TransactionManager $transactions,
|
|
71
|
+
private readonly CacheInterface $cache,
|
|
72
|
+
) {}
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Generic "Manager" / "Helper" Classes
|
|
77
|
+
|
|
78
|
+
```php
|
|
79
|
+
// BAD — vague name, accumulates unrelated methods
|
|
80
|
+
class UserManager
|
|
81
|
+
{
|
|
82
|
+
public function get(int $id) { /* ... */ }
|
|
83
|
+
public function update(User $user) { /* ... */ }
|
|
84
|
+
public function sendWelcomeEmail(User $user) { /* ... */ }
|
|
85
|
+
public function exportToCsv(array $users) { /* ... */ }
|
|
86
|
+
// ...
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// GOOD — focused services named by responsibility
|
|
90
|
+
final class FindUserById { /* ... */ }
|
|
91
|
+
final class UpdateUserProfile { /* ... */ }
|
|
92
|
+
final class SendWelcomeEmail { /* ... */ }
|
|
93
|
+
final class ExportUsersToCsv { /* ... */ }
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Fat Controllers / Fat Models
|
|
97
|
+
|
|
98
|
+
```php
|
|
99
|
+
// BAD — business logic in the controller
|
|
100
|
+
class InvoiceController
|
|
101
|
+
{
|
|
102
|
+
public function store(Request $request)
|
|
103
|
+
{
|
|
104
|
+
$data = $request->validate([/* ... */]);
|
|
105
|
+
$invoice = new Invoice($data);
|
|
106
|
+
$tax = $invoice->amount * 0.20;
|
|
107
|
+
$invoice->total = $invoice->amount + $tax;
|
|
108
|
+
$invoice->save();
|
|
109
|
+
Mail::to($invoice->customer->email)->send(new InvoiceCreatedMail($invoice));
|
|
110
|
+
Log::info("invoice.created", ['id' => $invoice->id]);
|
|
111
|
+
return response()->json($invoice);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// GOOD — controller delegates to an application handler
|
|
116
|
+
final class CreateInvoiceController
|
|
117
|
+
{
|
|
118
|
+
public function __construct(private readonly CreateInvoiceHandler $handler) {}
|
|
119
|
+
|
|
120
|
+
public function __invoke(CreateInvoiceRequest $request): JsonResponse
|
|
121
|
+
{
|
|
122
|
+
$invoice = $this->handler->handle(CreateInvoiceCommand::fromRequest($request));
|
|
123
|
+
return new JsonResponse(InvoiceResource::from($invoice), 201);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
## Trait Pyramids
|
|
129
|
+
|
|
130
|
+
```php
|
|
131
|
+
// BAD — traits stacking hidden behavior
|
|
132
|
+
class UserService
|
|
133
|
+
{
|
|
134
|
+
use HasCaching;
|
|
135
|
+
use HasLogging;
|
|
136
|
+
use HasEvents;
|
|
137
|
+
use HasValidation;
|
|
138
|
+
use HasNotifications;
|
|
139
|
+
use HasRetries;
|
|
140
|
+
// What does this class actually do? Where does behavior come from?
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// GOOD — composition
|
|
144
|
+
final class UserService
|
|
145
|
+
{
|
|
146
|
+
public function __construct(
|
|
147
|
+
private readonly Cache $cache,
|
|
148
|
+
private readonly LoggerInterface $logger,
|
|
149
|
+
private readonly EventDispatcher $events,
|
|
150
|
+
) {}
|
|
151
|
+
}
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
Use traits only for genuinely cross-cutting *implementation details* — not for sharing business behavior.
|
|
155
|
+
|
|
156
|
+
## Deep Inheritance
|
|
157
|
+
|
|
158
|
+
```php
|
|
159
|
+
// BAD — design smell
|
|
160
|
+
abstract class BaseController { /* ... */ }
|
|
161
|
+
abstract class ApiController extends BaseController { /* ... */ }
|
|
162
|
+
abstract class VersionedController extends ApiController { /* ... */ }
|
|
163
|
+
class UserController extends VersionedController { /* ... */ }
|
|
164
|
+
|
|
165
|
+
// GOOD — composition, often via middleware or invoke handlers
|
|
166
|
+
final class UserController
|
|
167
|
+
{
|
|
168
|
+
public function __construct(
|
|
169
|
+
private readonly ApiResponseFactory $responses,
|
|
170
|
+
private readonly UserFinder $users,
|
|
171
|
+
) {}
|
|
172
|
+
}
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
## Exceptions for Control Flow
|
|
176
|
+
|
|
177
|
+
```php
|
|
178
|
+
// BAD — exception as a return value
|
|
179
|
+
public function findUserByEmail(string $email): User
|
|
180
|
+
{
|
|
181
|
+
$user = $this->db->find($email);
|
|
182
|
+
if (!$user) {
|
|
183
|
+
throw new NotFoundException();
|
|
184
|
+
}
|
|
185
|
+
return $user;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// In caller:
|
|
189
|
+
try {
|
|
190
|
+
$user = $service->findUserByEmail($email);
|
|
191
|
+
} catch (NotFoundException) {
|
|
192
|
+
return null; // expected case, not exceptional
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// GOOD — nullable return for expected absence
|
|
196
|
+
public function findUserByEmail(string $email): ?User
|
|
197
|
+
{
|
|
198
|
+
return $this->db->find($email);
|
|
199
|
+
}
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
Reserve exceptions for genuinely exceptional situations: infrastructure failure, invariant violation, security breach.
|
|
203
|
+
|
|
204
|
+
## Mixed HTML/PHP
|
|
205
|
+
|
|
206
|
+
```php
|
|
207
|
+
<!-- BAD — PHP and HTML interleaved in business logic -->
|
|
208
|
+
<?php
|
|
209
|
+
$users = DB::query("SELECT * FROM users");
|
|
210
|
+
foreach ($users as $u) {
|
|
211
|
+
if ($u->active) {
|
|
212
|
+
echo "<div>" . htmlspecialchars($u->name) . "</div>";
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
?>
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
Use a template engine (Blade, Twig) for HTML. Keep PHP business logic in classes. Better yet, render the frontend in a SPA or HTMX layer.
|
|
219
|
+
|
|
220
|
+
## Ignoring Static Analysis
|
|
221
|
+
|
|
222
|
+
```php
|
|
223
|
+
// BAD — silently passing untyped data; PHPStan would catch this
|
|
224
|
+
function calculate($data)
|
|
225
|
+
{
|
|
226
|
+
return $data['amount'] * $data['tax_rate'];
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// GOOD — typed and PHPStan-verifiable
|
|
230
|
+
function calculate(InvoiceData $data): Money
|
|
231
|
+
{
|
|
232
|
+
return $data->amount->multiply($data->taxRate);
|
|
233
|
+
}
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
Without PHPStan at high levels, "modern PHP" is incomplete. See [`tooling.md`](tooling.md).
|
|
237
|
+
|
|
238
|
+
## Generic CRUD Repositories
|
|
239
|
+
|
|
240
|
+
See [`architecture.md`](architecture.md) for the full pattern. Short version:
|
|
241
|
+
|
|
242
|
+
```php
|
|
243
|
+
// BAD
|
|
244
|
+
$user = $userRepository->find($id);
|
|
245
|
+
$userRepository->save($user);
|
|
246
|
+
|
|
247
|
+
// Just use the ORM directly for this:
|
|
248
|
+
$user = User::find($id);
|
|
249
|
+
$user->save();
|
|
250
|
+
|
|
251
|
+
// Build a repository only when it expresses domain meaning:
|
|
252
|
+
$subscribers = $userRepository->findActiveSubscribers();
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
## See Also
|
|
256
|
+
|
|
257
|
+
- [`rules.md`](rules.md) — the positive patterns
|
|
258
|
+
- [`architecture.md`](architecture.md) — layers, DI, value objects
|
|
259
|
+
- [`tooling.md`](tooling.md) — PHPStan to catch many of these automatically
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
# PHP Architecture
|
|
2
|
+
|
|
3
|
+
## Core Rules
|
|
4
|
+
|
|
5
|
+
- Default to a **modular monolith** with domain-driven boundaries
|
|
6
|
+
- Separate **Domain**, **Application**, **Infrastructure**, and **Presentation** layers
|
|
7
|
+
- Domain layer never imports framework code
|
|
8
|
+
- Use **constructor injection** for all dependencies — no service locators, no global helpers, no static singletons
|
|
9
|
+
- Keep controllers and handlers thin
|
|
10
|
+
|
|
11
|
+
## Project Structure
|
|
12
|
+
|
|
13
|
+
### Standard (DDD-flavored)
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
src/
|
|
17
|
+
Domain/ ← business rules, entities, value objects, domain services
|
|
18
|
+
Application/ ← use case orchestration (commands, queries, handlers)
|
|
19
|
+
Infrastructure/ ← database, cache, external APIs, framework adapters
|
|
20
|
+
Presentation/ ← controllers, HTTP, validation, serialization
|
|
21
|
+
config/
|
|
22
|
+
bootstrap/
|
|
23
|
+
public/
|
|
24
|
+
storage/
|
|
25
|
+
tests/
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### Lightweight (smaller projects)
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
src/
|
|
32
|
+
Core/
|
|
33
|
+
Services/
|
|
34
|
+
Repositories/
|
|
35
|
+
Http/
|
|
36
|
+
Support/
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Prefer clear boundaries over deep nesting. If you find yourself creating `src/Everything/Managers/Helpers/Utilities/Common/Shared/`, the architecture has become unclear.
|
|
40
|
+
|
|
41
|
+
## Layer Responsibilities
|
|
42
|
+
|
|
43
|
+
### Domain
|
|
44
|
+
|
|
45
|
+
Contains the business model. Pure PHP. No framework imports.
|
|
46
|
+
|
|
47
|
+
- Entities (`Order`, `Invoice`, `Subscription`)
|
|
48
|
+
- Value objects (`Money`, `EmailAddress`)
|
|
49
|
+
- Domain services (logic that doesn't naturally fit on an entity)
|
|
50
|
+
- Domain events (`InvoicePaid`, `SubscriptionCancelled`)
|
|
51
|
+
|
|
52
|
+
Must NOT depend on:
|
|
53
|
+
- Frameworks (Laravel, Symfony)
|
|
54
|
+
- HTTP
|
|
55
|
+
- Databases
|
|
56
|
+
- Redis
|
|
57
|
+
- Queues
|
|
58
|
+
- External APIs
|
|
59
|
+
|
|
60
|
+
### Application
|
|
61
|
+
|
|
62
|
+
Coordinates use cases. Thin orchestration.
|
|
63
|
+
|
|
64
|
+
- Commands (`CreateInvoice`, `RefundOrder`) — write operations
|
|
65
|
+
- Queries (`GetActiveSubscribers`) — read operations
|
|
66
|
+
- Handlers (one per command/query)
|
|
67
|
+
- Transactional boundaries
|
|
68
|
+
|
|
69
|
+
The Application layer calls domain services and infrastructure adapters. It doesn't contain business logic itself.
|
|
70
|
+
|
|
71
|
+
### Infrastructure
|
|
72
|
+
|
|
73
|
+
Framework-aware glue.
|
|
74
|
+
|
|
75
|
+
- Database access (Eloquent, Doctrine, raw PDO)
|
|
76
|
+
- Cache (Redis adapters)
|
|
77
|
+
- External API clients
|
|
78
|
+
- Queue adapters
|
|
79
|
+
- Email sender implementations
|
|
80
|
+
- Framework integration (service providers, container bindings)
|
|
81
|
+
|
|
82
|
+
### Presentation
|
|
83
|
+
|
|
84
|
+
The edge.
|
|
85
|
+
|
|
86
|
+
- Controllers (HTTP entry points)
|
|
87
|
+
- Form requests / request DTOs
|
|
88
|
+
- Response serialization
|
|
89
|
+
- API resource transformers
|
|
90
|
+
|
|
91
|
+
Keep controllers thin:
|
|
92
|
+
|
|
93
|
+
```php
|
|
94
|
+
final class CreateInvoiceController
|
|
95
|
+
{
|
|
96
|
+
public function __construct(
|
|
97
|
+
private readonly CreateInvoiceHandler $handler,
|
|
98
|
+
) {}
|
|
99
|
+
|
|
100
|
+
public function __invoke(CreateInvoiceRequest $request): JsonResponse
|
|
101
|
+
{
|
|
102
|
+
$invoice = $this->handler->handle(CreateInvoiceCommand::fromRequest($request));
|
|
103
|
+
return new JsonResponse(InvoiceResource::from($invoice), 201);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## Dependency Injection
|
|
109
|
+
|
|
110
|
+
### Constructor Injection (Preferred)
|
|
111
|
+
|
|
112
|
+
```php
|
|
113
|
+
final class OrderService
|
|
114
|
+
{
|
|
115
|
+
public function __construct(
|
|
116
|
+
private readonly PaymentGateway $payments,
|
|
117
|
+
private readonly OrderRepository $orders,
|
|
118
|
+
private readonly EventDispatcher $events,
|
|
119
|
+
) {}
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### Avoid
|
|
124
|
+
|
|
125
|
+
- `app()`, `resolve()`, `Container::get()` inside business code (service locator)
|
|
126
|
+
- Facades inside domain or application layers
|
|
127
|
+
- `static` factory methods that reach into the container
|
|
128
|
+
- Global helpers (`auth()`, `request()`, `now()`) in domain/application code
|
|
129
|
+
|
|
130
|
+
Constructor injection makes dependencies explicit, testable, and substitutable.
|
|
131
|
+
|
|
132
|
+
## Value Objects
|
|
133
|
+
|
|
134
|
+
Wrap primitives that have rules or constraints:
|
|
135
|
+
|
|
136
|
+
```php
|
|
137
|
+
final readonly class EmailAddress
|
|
138
|
+
{
|
|
139
|
+
public function __construct(public string $value)
|
|
140
|
+
{
|
|
141
|
+
if (!filter_var($value, FILTER_VALIDATE_EMAIL)) {
|
|
142
|
+
throw new InvalidArgumentException("Invalid email: $value");
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
public function __toString(): string
|
|
147
|
+
{
|
|
148
|
+
return $this->value;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
The value object guarantees its invariants — once you hold one, you know it's valid.
|
|
154
|
+
|
|
155
|
+
## DTOs
|
|
156
|
+
|
|
157
|
+
For boundaries — HTTP requests, queue jobs, service-to-service calls:
|
|
158
|
+
|
|
159
|
+
```php
|
|
160
|
+
final readonly class CreateUserDTO
|
|
161
|
+
{
|
|
162
|
+
public function __construct(
|
|
163
|
+
public string $name,
|
|
164
|
+
public EmailAddress $email,
|
|
165
|
+
) {}
|
|
166
|
+
}
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
DTOs are simple data carriers. They don't have business behavior. Methods are limited to construction (`fromRequest`, `fromArray`) and serialization (`toArray`).
|
|
170
|
+
|
|
171
|
+
## Repositories
|
|
172
|
+
|
|
173
|
+
Repositories are useful **only** when they encode domain meaning. Generic CRUD wrappers are not useful.
|
|
174
|
+
|
|
175
|
+
```php
|
|
176
|
+
// BAD — generic CRUD, mirrors ORM
|
|
177
|
+
interface UserRepository
|
|
178
|
+
{
|
|
179
|
+
public function find(int $id): ?User;
|
|
180
|
+
public function save(User $user): void;
|
|
181
|
+
public function delete(User $user): void;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// GOOD — domain-shaped queries
|
|
185
|
+
interface UserRepository
|
|
186
|
+
{
|
|
187
|
+
public function findActiveSubscribers(): UserCollection;
|
|
188
|
+
public function findByEmail(EmailAddress $email): ?User;
|
|
189
|
+
public function countNewThisMonth(): int;
|
|
190
|
+
}
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
If a repository's methods read like an ORM, don't bother — use the ORM directly.
|
|
194
|
+
|
|
195
|
+
## Async Processing
|
|
196
|
+
|
|
197
|
+
Use queues for:
|
|
198
|
+
- Email and notifications
|
|
199
|
+
- Webhook dispatch
|
|
200
|
+
- Image / video / file processing
|
|
201
|
+
- AI jobs (inference, embedding pipelines)
|
|
202
|
+
- Reports
|
|
203
|
+
- Any long-running operation
|
|
204
|
+
|
|
205
|
+
Common queue backends: Redis, RabbitMQ, SQS. Laravel Horizon provides excellent queue observability.
|
|
206
|
+
|
|
207
|
+
Avoid synchronous HTTP chains for work that doesn't need to be inline.
|
|
208
|
+
|
|
209
|
+
## Modular Monolith First
|
|
210
|
+
|
|
211
|
+
A well-structured modular monolith outperforms poorly-managed microservices in almost every case.
|
|
212
|
+
|
|
213
|
+
- Start as a single deployable
|
|
214
|
+
- Enforce module boundaries internally
|
|
215
|
+
- Extract a service only when there's a clear scaling or team-ownership reason
|
|
216
|
+
|
|
217
|
+
See [`../architecture/refactoring.md`](../architecture/refactoring.md) — extraction is a refactor, not a fresh build.
|
|
218
|
+
|
|
219
|
+
## DO NOT
|
|
220
|
+
|
|
221
|
+
- Put business logic in routes or controllers
|
|
222
|
+
- Make the domain layer depend on a framework
|
|
223
|
+
- Reach for CQRS, event sourcing, or microservices because they "sound advanced"
|
|
224
|
+
- Build deep inheritance chains (`BaseController → ApiController → VersionedController → UserController`)
|
|
225
|
+
- Use traits to share business behavior — use composition
|
|
226
|
+
|
|
227
|
+
## PRIORITY
|
|
228
|
+
|
|
229
|
+
```
|
|
230
|
+
Layered boundaries > Framework convenience
|
|
231
|
+
Constructor injection > Service locator / globals
|
|
232
|
+
Domain-shaped repositories > Generic CRUD wrappers
|
|
233
|
+
Modular monolith > Premature microservices
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
## See Also
|
|
237
|
+
|
|
238
|
+
- [`rules.md`](rules.md) — strict typing for the layered code
|
|
239
|
+
- [`anti-patterns.md`](anti-patterns.md) — what breaks layered architecture
|
|
240
|
+
- [`frameworks.md`](frameworks.md) — framework-specific guidance
|
|
241
|
+
- [`../architecture/refactoring.md`](../architecture/refactoring.md) — extracting modules
|