start-vibing-stacks 2.1.1 → 2.3.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.
@@ -1,8 +1,8 @@
1
- # PHP 8.3+ Patterns
1
+ # PHP 8.3+ Patterns for Laravel
2
2
 
3
3
  ## Version Requirements
4
4
 
5
- - **PHP >= 8.3** — MANDATORY. Do not use deprecated patterns.
5
+ - **PHP >= 8.3** — MANDATORY. Use all modern features.
6
6
  - **Composer >= 2.0** — For dependency management.
7
7
  - Use strict types: `declare(strict_types=1);` in EVERY file.
8
8
 
@@ -11,32 +11,48 @@
11
11
  ### Typed Properties & Constructor Promotion
12
12
 
13
13
  ```php
14
- class User {
14
+ class CreateUserDTO {
15
15
  public function __construct(
16
- private readonly string $name,
17
- private readonly string $email,
18
- private readonly int $age,
16
+ public readonly string $name,
17
+ public readonly string $email,
18
+ public readonly int $age,
19
19
  ) {}
20
20
  }
21
21
  ```
22
22
 
23
- ### Enums
23
+ ### Enums (Use for Status, Types, Roles)
24
24
 
25
25
  ```php
26
- enum Status: string {
27
- case Active = 'active';
28
- case Inactive = 'inactive';
29
- case Suspended = 'suspended';
26
+ enum OrderStatus: string {
27
+ case Pending = 'pending';
28
+ case Processing = 'processing';
29
+ case Completed = 'completed';
30
+ case Cancelled = 'cancelled';
31
+
32
+ public function label(): string {
33
+ return match($this) {
34
+ self::Pending => 'Awaiting Processing',
35
+ self::Processing => 'In Progress',
36
+ self::Completed => 'Done',
37
+ self::Cancelled => 'Cancelled',
38
+ };
39
+ }
30
40
  }
41
+
42
+ // In Eloquent model:
43
+ protected $casts = [
44
+ 'status' => OrderStatus::class,
45
+ ];
31
46
  ```
32
47
 
33
- ### Readonly Classes (8.2+)
48
+ ### Readonly Classes for DTOs
34
49
 
35
50
  ```php
36
- readonly class UserDTO {
51
+ readonly class PaymentResult {
37
52
  public function __construct(
38
- public string $name,
39
- public string $email,
53
+ public string $transactionId,
54
+ public float $amount,
55
+ public bool $success,
40
56
  ) {}
41
57
  }
42
58
  ```
@@ -44,45 +60,103 @@ readonly class UserDTO {
44
60
  ### Typed Class Constants (8.3+)
45
61
 
46
62
  ```php
47
- class Config {
48
- public const string APP_NAME = 'MyApp';
49
- public const int MAX_RETRIES = 3;
63
+ class RateLimiter {
64
+ public const int MAX_ATTEMPTS = 5;
65
+ public const int DECAY_SECONDS = 60;
66
+ public const string CACHE_PREFIX = 'rate_limit';
50
67
  }
51
68
  ```
52
69
 
53
- ### Match Expression
70
+ ### Match Expression (Prefer over switch)
54
71
 
55
72
  ```php
56
- $result = match($status) {
57
- 'active' => handleActive(),
58
- 'inactive' => handleInactive(),
59
- default => throw new \InvalidArgumentException("Unknown status: {$status}"),
73
+ $result = match($request->input('action')) {
74
+ 'approve' => $service->approve($record),
75
+ 'reject' => $service->reject($record),
76
+ 'escalate' => $service->escalate($record),
77
+ default => throw new \InvalidArgumentException("Unknown action"),
60
78
  };
61
79
  ```
62
80
 
63
81
  ### Named Arguments
64
82
 
65
83
  ```php
66
- $response = new Response(
67
- content: $html,
84
+ $response = Response::json(
85
+ data: $collection,
68
86
  status: 200,
69
- headers: ['Content-Type' => 'text/html'],
87
+ headers: ['X-Total-Count' => $total],
70
88
  );
71
89
  ```
72
90
 
73
91
  ### Null-safe Operator
74
92
 
75
93
  ```php
76
- $country = $user?->address?->country?->name;
94
+ $country = $user?->address?->country?->name ?? 'Unknown';
95
+ ```
96
+
97
+ ### First-class Callable Syntax
98
+
99
+ ```php
100
+ $filtered = $collection->filter($this->isEligible(...));
101
+ ```
102
+
103
+ ## Clean Code Patterns
104
+
105
+ ### Dependency Injection (Octane-safe)
106
+
107
+ ```php
108
+ // CORRECT: Constructor injection
109
+ class OrderService {
110
+ public function __construct(
111
+ private readonly PaymentGateway $gateway,
112
+ private readonly OrderRepository $orders,
113
+ private readonly LoggerInterface $logger,
114
+ ) {}
115
+ }
116
+
117
+ // WRONG: Service locator
118
+ class OrderService {
119
+ public function process(): void {
120
+ $gateway = app(PaymentGateway::class); // Avoid in Octane
121
+ }
122
+ }
77
123
  ```
78
124
 
79
- ### Fibers (8.1+)
125
+ ### Service Architecture
126
+
127
+ ```
128
+ App\Services\
129
+ ├── UserService.php # Core user operations
130
+ ├── PaymentService.php # Payment processing
131
+ └── AdPlatforms\
132
+ ├── AdPlatformService.php # Main service
133
+ └── Helpers\
134
+ ├── GoogleAdsHelper.php # Extracted complex logic
135
+ └── MetaAdsHelper.php
136
+ ```
137
+
138
+ **Rule:** When a service class exceeds ~200 lines, extract specific logic into `Helpers` sub-namespace.
139
+
140
+ ### Return Types & Exceptions
80
141
 
81
142
  ```php
82
- $fiber = new Fiber(function (): void {
83
- $value = Fiber::suspend('fiber started');
84
- echo "Resumed with: {$value}";
85
- });
143
+ public function findOrFail(string $id): User
144
+ {
145
+ return User::findOrFail($id);
146
+ }
147
+
148
+ public function process(Order $order): PaymentResult
149
+ {
150
+ try {
151
+ return $this->gateway->charge($order);
152
+ } catch (GatewayException $e) {
153
+ $this->logger->error('Payment failed', [
154
+ 'order_id' => $order->id,
155
+ 'error' => $e->getMessage(),
156
+ ]);
157
+ throw new PaymentFailedException($order, $e);
158
+ }
159
+ }
86
160
  ```
87
161
 
88
162
  ## FORBIDDEN Patterns
@@ -92,28 +166,14 @@ $fiber = new Fiber(function (): void {
92
166
  | `$var = isset($x) ? $x : $default` | `$var = $x ?? $default` |
93
167
  | `function foo($x)` (no types) | `function foo(string $x): void` |
94
168
  | `array()` | `[]` |
95
- | `mysql_*` functions | PDO or Doctrine |
96
- | Global variables | Dependency injection |
97
- | `eval()` | Never use eval |
98
169
  | Untyped properties | Always type properties |
99
-
100
- ## Error Handling
101
-
102
- ```php
103
- try {
104
- $result = riskyOperation();
105
- } catch (SpecificException $e) {
106
- logger()->error('Operation failed', [
107
- 'error' => $e->getMessage(),
108
- 'trace' => $e->getTraceAsString(),
109
- ]);
110
- throw new DomainException('Friendly message', 0, $e);
111
- }
112
- ```
170
+ | `static` properties on services | Instance properties (Octane-safe) |
171
+ | Global variables | Dependency injection |
172
+ | `switch` with simple mapping | `match` expression |
173
+ | Large service classes (200+ lines) | Extract into Helpers |
174
+ | `mixed` type without justification | Use specific types or union types |
113
175
 
114
176
  ## PSR Standards
115
177
 
116
- - **PSR-4**: Autoloading
117
- - **PSR-7**: HTTP Messages (if not using framework)
118
- - **PSR-12**: Coding Style
119
- - **PSR-15**: HTTP Handlers
178
+ - **PSR-4**: Autoloading (Laravel default)
179
+ - **PSR-12**: Coding Style (enforced by PHP-CS-Fixer)
@@ -1,80 +1,198 @@
1
- # PHP Security Scan
1
+ # Laravel Security Scan
2
2
 
3
- ## OWASP Top 10 for PHP
3
+ ## OWASP Top 10 for Laravel
4
4
 
5
5
  ### A01 - Broken Access Control
6
6
 
7
7
  ```php
8
- // NEVER trust user input for authorization
9
- $userId = $_GET['user_id'];
8
+ // WRONG: Trust user input for authorization
9
+ $userId = $request->input('user_id');
10
10
  $user = User::find($userId);
11
11
 
12
- // Use authenticated session
13
- $user = Auth::user();
12
+ // CORRECT: Use authenticated session
13
+ $user = $request->user();
14
+
15
+ // CORRECT: Scope queries by authenticated user
16
+ $query = Resource::query();
17
+ if (!$request->user()->isAdmin()) {
18
+ $query->where('user_id', $request->user()->id);
19
+ }
14
20
  ```
15
21
 
22
+ **Rules:**
23
+ - NEVER return unscoped queries — always filter by authenticated user
24
+ - Use Policies for authorization logic
25
+ - Use Form Requests with `authorize()` method
26
+ - Use Route Model Binding with scoping
27
+
16
28
  ### A02 - Cryptographic Failures
17
29
 
18
30
  ```php
19
- // ❌ NEVER
31
+ // WRONG
20
32
  md5($password);
21
33
  sha1($password);
22
34
 
23
- // ALWAYS
24
- password_hash($password, PASSWORD_BCRYPT, ['cost' => 12]);
25
- password_verify($input, $hash);
35
+ // CORRECT (Laravel handles this via Hash facade)
36
+ Hash::make($password);
37
+ Hash::check($input, $hashedPassword);
38
+
39
+ // For API tokens: use Laravel Sanctum
40
+ // Table: users_access_tokens (UUIDs + Soft Deletes)
41
+ // All token actions logged via Loggable trait
26
42
  ```
27
43
 
28
44
  ### A03 - Injection
29
45
 
30
46
  ```php
31
- // SQL Injection
32
- $db->query("SELECT * FROM users WHERE id = {$_GET['id']}");
47
+ // WRONG: SQL Injection via DB::raw
48
+ $users = DB::select("SELECT * FROM users WHERE id = {$id}");
49
+ DB::raw("WHERE name = '{$name}'");
33
50
 
34
- // Prepared statements (PDO)
35
- $stmt = $db->prepare("SELECT * FROM users WHERE id = :id");
36
- $stmt->execute([':id' => $id]);
51
+ // CORRECT: Eloquent (preferred)
52
+ $user = User::findOrFail($id);
37
53
 
38
- // Prepared statements (MySQLi)
39
- $stmt = $db->prepare("SELECT * FROM users WHERE id = ?");
40
- $stmt->bind_param('i', $id);
41
- $stmt->execute();
54
+ // CORRECT: Query Builder with bindings
55
+ $users = DB::table('users')->where('name', $name)->get();
56
+
57
+ // CORRECT: Raw with parameter binding (last resort)
58
+ $results = DB::select('SELECT * FROM users WHERE id = ?', [$id]);
42
59
  ```
43
60
 
44
61
  ### A07 - XSS Prevention
45
62
 
46
63
  ```php
47
- // Raw output
48
- echo $userInput;
64
+ // WRONG: Unescaped output in Blade
65
+ {!! $userInput !!}
49
66
 
50
- // Always escape
51
- echo htmlspecialchars($userInput, ENT_QUOTES, 'UTF-8');
67
+ // CORRECT: Auto-escaped (Blade default)
68
+ {{ $userInput }}
52
69
 
53
- // In templates (Blade)
54
- {{ $userInput }} // Auto-escaped
55
- {!! $trustedHtml !!} // Only for trusted content
70
+ // Only use {!! !!} for TRUSTED, pre-sanitized HTML
71
+ {!! $trustedHtmlFromCMS !!}
56
72
  ```
57
73
 
58
- ## Security Checklist
74
+ ### A08 - Insecure Deserialization
75
+
76
+ ```php
77
+ // WRONG: Unserialize user data
78
+ $data = unserialize($request->input('data'));
79
+
80
+ // CORRECT: Use JSON with Model casts
81
+ protected $casts = [
82
+ 'metadata' => 'array',
83
+ 'settings' => 'array',
84
+ ];
85
+
86
+ // CORRECT: Defensive JSON handling
87
+ $data = is_string($model->data)
88
+ ? json_decode($model->data, true)
89
+ : $model->data;
90
+ ```
91
+
92
+ ## Laravel-Specific Security
93
+
94
+ ### Mass Assignment Protection
95
+
96
+ ```php
97
+ // WRONG: No protection
98
+ $user = User::create($request->all());
99
+
100
+ // CORRECT: Define $fillable
101
+ class User extends Model {
102
+ protected $fillable = ['name', 'email'];
103
+ }
104
+
105
+ // CORRECT: Use Form Request validated data
106
+ $user = User::create($request->validated());
107
+ ```
108
+
109
+ ### Environment Variables
110
+
111
+ ```php
112
+ // WRONG: env() outside config files (null when cached)
113
+ $apiKey = env('API_KEY');
59
114
 
60
- - [ ] All SQL uses prepared statements
61
- - [ ] All output is escaped (htmlspecialchars)
62
- - [ ] CSRF tokens on all forms
63
- - [ ] Passwords use password_hash/verify
64
- - [ ] File uploads validated (type, size, extension)
65
- - [ ] Sessions use httpOnly + secure cookies
66
- - [ ] Input validated before processing
67
- - [ ] No `eval()`, `exec()` with user input
68
- - [ ] No `extract()` on user data
69
- - [ ] Headers: X-Content-Type-Options, X-Frame-Options, CSP
115
+ // CORRECT: Use config files
116
+ // config/services.php
117
+ 'stripe' => ['key' => env('STRIPE_KEY')],
118
+
119
+ // In code:
120
+ $apiKey = config('services.stripe.key');
121
+ ```
122
+
123
+ ### CSRF & Authentication
124
+
125
+ ```php
126
+ // Blade forms — CSRF automatic
127
+ <form method="POST">
128
+ @csrf
129
+ ...
130
+ </form>
131
+
132
+ // API routes — use Sanctum middleware
133
+ Route::middleware('auth:sanctum')->group(function () {
134
+ Route::apiResource('users', UserController::class);
135
+ });
136
+ ```
137
+
138
+ ### File Upload Validation
139
+
140
+ ```php
141
+ public function rules(): array
142
+ {
143
+ return [
144
+ 'avatar' => [
145
+ 'required',
146
+ 'image',
147
+ 'mimes:jpg,jpeg,png,webp',
148
+ 'max:2048', // 2MB
149
+ 'dimensions:max_width=2000,max_height=2000',
150
+ ],
151
+ ];
152
+ }
153
+ ```
154
+
155
+ ## Octane Security Considerations
156
+
157
+ ```php
158
+ // WRONG: Static state leaks user data between requests
159
+ class AuthService {
160
+ private static ?User $currentUser = null; // LEAKS!
161
+ }
162
+
163
+ // WRONG: Superglobals are stale in Octane
164
+ $token = $_SERVER['HTTP_AUTHORIZATION'];
165
+
166
+ // CORRECT: Use Request object
167
+ $token = $request->bearerToken();
168
+ $user = $request->user();
169
+ ```
170
+
171
+ ## Security Checklist
70
172
 
71
- ## Sensitive Data Patterns (FORBIDDEN)
173
+ - [ ] All queries use Eloquent or Query Builder (no raw SQL with user input)
174
+ - [ ] All Blade output uses `{{ }}` (auto-escaped)
175
+ - [ ] CSRF protection on all forms (`@csrf`)
176
+ - [ ] Passwords use `Hash::make()` / `Hash::check()`
177
+ - [ ] File uploads validated (type, size, dimensions)
178
+ - [ ] API routes protected with Sanctum middleware
179
+ - [ ] Models define `$fillable` (no `$guarded = []`)
180
+ - [ ] Queries scoped by authenticated user (unless admin)
181
+ - [ ] No `env()` calls outside config files
182
+ - [ ] No static state in services (Octane-safe)
183
+ - [ ] No superglobals (`$_GET`, `$_POST`, `$_SESSION`)
184
+ - [ ] Sensitive headers set (X-Content-Type-Options, X-Frame-Options, CSP)
185
+ - [ ] Rate limiting on authentication endpoints
186
+
187
+ ## Sensitive Patterns (FORBIDDEN)
72
188
 
73
189
  | Pattern | Risk |
74
190
  |---------|------|
75
- | `$_GET` used directly in SQL | SQL Injection |
76
- | `echo $_POST[...]` | XSS |
191
+ | `DB::raw()` with user input | SQL Injection |
192
+ | `{!! $userInput !!}` | XSS |
77
193
  | `md5()` / `sha1()` for passwords | Weak hashing |
78
- | `eval($userInput)` | RCE |
79
- | `include $_GET['page']` | LFI/RFI |
80
- | `serialize()` user data | Object injection |
194
+ | Dynamic code execution | RCE |
195
+ | `unserialize()` on user data | Object injection |
196
+ | `$request->all()` without `$fillable` | Mass assignment |
197
+ | `env()` in runtime code | Null after config cache |
198
+ | Static user state in services | Data leaks in Octane |
@@ -27,13 +27,20 @@
27
27
  "name": "Laravel 12 + Octane (RoadRunner) + Inertia.js",
28
28
  "icon": "🚀",
29
29
  "detectFiles": ["artisan", "rr.yaml"],
30
- "default": true
30
+ "default": true,
31
+ "skills": [
32
+ "laravel-patterns",
33
+ "laravel-octane"
34
+ ]
31
35
  },
32
36
  {
33
37
  "id": "laravel",
34
38
  "name": "Laravel 12 (standard)",
35
39
  "icon": "🏗️",
36
- "detectFiles": ["artisan", "bootstrap/app.php"]
40
+ "detectFiles": ["artisan", "bootstrap/app.php"],
41
+ "skills": [
42
+ "laravel-patterns"
43
+ ]
37
44
  }
38
45
  ],
39
46
  "databases": [
@@ -46,12 +53,20 @@
46
53
  "id": "react-inertia",
47
54
  "name": "ReactJS 19 + Inertia.js + TailwindCSS 4",
48
55
  "icon": "⚛️",
49
- "default": true
56
+ "default": true,
57
+ "frameworks": ["laravel", "laravel-octane"]
50
58
  },
51
59
  {
52
60
  "id": "blade",
53
61
  "name": "Blade + TailwindCSS 4",
54
- "icon": "🖼️"
62
+ "icon": "🖼️",
63
+ "frameworks": ["laravel", "laravel-octane"]
64
+ },
65
+ {
66
+ "id": "livewire",
67
+ "name": "Livewire + Alpine.js",
68
+ "icon": "⚡",
69
+ "frameworks": ["laravel", "laravel-octane"]
55
70
  },
56
71
  {
57
72
  "id": "none",
@@ -68,8 +83,6 @@
68
83
  "phpstan-analysis",
69
84
  "composer-workflow",
70
85
  "security-scan-php",
71
- "laravel-patterns",
72
- "laravel-octane",
73
86
  "api-design"
74
87
  ],
75
88
  "requirements": [