ima-claude 2.9.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.
Files changed (182) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +463 -0
  3. package/dist/cli.js +1064 -0
  4. package/package.json +49 -0
  5. package/platforms/claude/adapter.ts +115 -0
  6. package/platforms/junie/adapter.ts +254 -0
  7. package/platforms/junie/agents-template.md +113 -0
  8. package/platforms/junie/hook-translations.md +84 -0
  9. package/platforms/shared/detector.ts +27 -0
  10. package/platforms/shared/installer.ts +202 -0
  11. package/platforms/shared/types.ts +78 -0
  12. package/plugins/ima-claude/.claude-plugin/plugin.json +25 -0
  13. package/plugins/ima-claude/agents/explorer.md +30 -0
  14. package/plugins/ima-claude/agents/implementer.md +30 -0
  15. package/plugins/ima-claude/agents/memory.md +42 -0
  16. package/plugins/ima-claude/agents/reviewer.md +53 -0
  17. package/plugins/ima-claude/agents/tester.md +33 -0
  18. package/plugins/ima-claude/agents/wp-developer.md +46 -0
  19. package/plugins/ima-claude/hooks/README.md +145 -0
  20. package/plugins/ima-claude/hooks/atlassian_prereqs.py +112 -0
  21. package/plugins/ima-claude/hooks/block_sed_edits.py +59 -0
  22. package/plugins/ima-claude/hooks/bootstrap.sh +90 -0
  23. package/plugins/ima-claude/hooks/bootstrap_utility_check.py +94 -0
  24. package/plugins/ima-claude/hooks/composer_autoload_check.py +70 -0
  25. package/plugins/ima-claude/hooks/docs_organization.py +104 -0
  26. package/plugins/ima-claude/hooks/enforce_rg_over_grep.py +56 -0
  27. package/plugins/ima-claude/hooks/fp_utility_check.py +90 -0
  28. package/plugins/ima-claude/hooks/hook_logger.py +69 -0
  29. package/plugins/ima-claude/hooks/hooks.json +239 -0
  30. package/plugins/ima-claude/hooks/jira_issue_fetch.py +79 -0
  31. package/plugins/ima-claude/hooks/jquery_in_wordpress.py +92 -0
  32. package/plugins/ima-claude/hooks/memory_bootstrap.py +79 -0
  33. package/plugins/ima-claude/hooks/memory_store_reminder.py +75 -0
  34. package/plugins/ima-claude/hooks/prompt_coach.py +125 -0
  35. package/plugins/ima-claude/hooks/prompt_coach_digest.md +48 -0
  36. package/plugins/ima-claude/hooks/prompt_coach_system.md +30 -0
  37. package/plugins/ima-claude/hooks/sequential_thinking_check.py +81 -0
  38. package/plugins/ima-claude/hooks/serena_over_grep.py +96 -0
  39. package/plugins/ima-claude/hooks/serena_over_read.py +66 -0
  40. package/plugins/ima-claude/hooks/serena_project_check.py +133 -0
  41. package/plugins/ima-claude/hooks/sql_injection_check.py +73 -0
  42. package/plugins/ima-claude/hooks/task_master_after_plan.py +31 -0
  43. package/plugins/ima-claude/hooks/task_master_before_impl.py +93 -0
  44. package/plugins/ima-claude/hooks/tavily_extract_advanced.py +48 -0
  45. package/plugins/ima-claude/hooks/vestige_before_external.py +86 -0
  46. package/plugins/ima-claude/hooks/webfetch_to_tavily.py +42 -0
  47. package/plugins/ima-claude/hooks/websearch_to_tavily.py +41 -0
  48. package/plugins/ima-claude/hooks/wp_security_check.py +150 -0
  49. package/plugins/ima-claude/personalities/README.md +45 -0
  50. package/plugins/ima-claude/personalities/enable-40k.md +69 -0
  51. package/plugins/ima-claude/personalities/enable-templars.md +69 -0
  52. package/plugins/ima-claude/skills/.research-summary.md +340 -0
  53. package/plugins/ima-claude/skills/architect/SKILL.md +304 -0
  54. package/plugins/ima-claude/skills/compound-bridge/SKILL.md +200 -0
  55. package/plugins/ima-claude/skills/discourse/SKILL.md +440 -0
  56. package/plugins/ima-claude/skills/discourse-admin/SKILL.md +192 -0
  57. package/plugins/ima-claude/skills/discourse-admin/references/api-endpoints.md +441 -0
  58. package/plugins/ima-claude/skills/discourse-admin/references/gotchas.md +107 -0
  59. package/plugins/ima-claude/skills/discourse-admin/references/staging-defaults.md +98 -0
  60. package/plugins/ima-claude/skills/discourse-admin/scripts/discourse-admin.py +319 -0
  61. package/plugins/ima-claude/skills/docs-organize/SKILL.md +254 -0
  62. package/plugins/ima-claude/skills/docs-organize/templates/active-README.md +50 -0
  63. package/plugins/ima-claude/skills/docs-organize/templates/archive-README.md +57 -0
  64. package/plugins/ima-claude/skills/docs-organize/templates/docs-README.md +43 -0
  65. package/plugins/ima-claude/skills/docs-organize/templates/phase-archive-README.md +83 -0
  66. package/plugins/ima-claude/skills/docs-organize/templates/section-README.md +48 -0
  67. package/plugins/ima-claude/skills/docs-organize/templates/transient-README.md +79 -0
  68. package/plugins/ima-claude/skills/docs-organize/templates/transient-gitignore +9 -0
  69. package/plugins/ima-claude/skills/ember-discourse/SKILL.md +496 -0
  70. package/plugins/ima-claude/skills/functional-programmer/SKILL.md +258 -0
  71. package/plugins/ima-claude/skills/ima-bootstrap/SKILL.md +278 -0
  72. package/plugins/ima-claude/skills/ima-bootstrap/references/bootstrap-patterns.md +356 -0
  73. package/plugins/ima-claude/skills/ima-bootstrap/references/ima-brand.md +273 -0
  74. package/plugins/ima-claude/skills/ima-bootstrap/references/theme-integration.md +212 -0
  75. package/plugins/ima-claude/skills/ima-brand/SKILL.md +108 -0
  76. package/plugins/ima-claude/skills/ima-brand/references/brand-identity.md +140 -0
  77. package/plugins/ima-claude/skills/ima-brand/references/digital-standards.md +180 -0
  78. package/plugins/ima-claude/skills/ima-brand/references/visual-system.md +173 -0
  79. package/plugins/ima-claude/skills/ima-forms-expert/SKILL.md +175 -0
  80. package/plugins/ima-claude/skills/ima-forms-expert/references/container-components.md +154 -0
  81. package/plugins/ima-claude/skills/ima-forms-expert/references/examples.md +328 -0
  82. package/plugins/ima-claude/skills/ima-forms-expert/references/field-components.md +298 -0
  83. package/plugins/ima-claude/skills/ima-forms-expert/references/form-factory.md +193 -0
  84. package/plugins/ima-claude/skills/ima-forms-expert/references/quick-reference.md +153 -0
  85. package/plugins/ima-claude/skills/ima-forms-expert/references/validation-engine.md +336 -0
  86. package/plugins/ima-claude/skills/jira-checkpoint/SKILL.md +178 -0
  87. package/plugins/ima-claude/skills/jquery/SKILL.md +413 -0
  88. package/plugins/ima-claude/skills/js-fp/SKILL.md +463 -0
  89. package/plugins/ima-claude/skills/js-fp/core-principles.md +487 -0
  90. package/plugins/ima-claude/skills/js-fp/examples/pure-functions.js +260 -0
  91. package/plugins/ima-claude/skills/js-fp/examples/tests/pure-functions.test.js +262 -0
  92. package/plugins/ima-claude/skills/js-fp/references/anti-patterns.md +120 -0
  93. package/plugins/ima-claude/skills/js-fp/references/performance-patterns.md +116 -0
  94. package/plugins/ima-claude/skills/js-fp/references/testing-patterns.md +134 -0
  95. package/plugins/ima-claude/skills/js-fp-api/SKILL.md +280 -0
  96. package/plugins/ima-claude/skills/js-fp-api/examples/crud-endpoint.js +258 -0
  97. package/plugins/ima-claude/skills/js-fp-api/references/middleware-patterns.md +134 -0
  98. package/plugins/ima-claude/skills/js-fp-api/references/security-sql.md +110 -0
  99. package/plugins/ima-claude/skills/js-fp-api/references/validation-patterns.md +165 -0
  100. package/plugins/ima-claude/skills/js-fp-react/SKILL.md +447 -0
  101. package/plugins/ima-claude/skills/js-fp-react/examples/ProductCard.tsx +65 -0
  102. package/plugins/ima-claude/skills/js-fp-react/references/hooks-advanced.md +136 -0
  103. package/plugins/ima-claude/skills/js-fp-react/references/performance-patterns.md +175 -0
  104. package/plugins/ima-claude/skills/js-fp-vue/SKILL.md +322 -0
  105. package/plugins/ima-claude/skills/js-fp-vue/references/complete-examples.md +397 -0
  106. package/plugins/ima-claude/skills/js-fp-vue/references/composables-advanced.md +282 -0
  107. package/plugins/ima-claude/skills/js-fp-vue/references/reactivity-patterns.md +348 -0
  108. package/plugins/ima-claude/skills/js-fp-vue/references/testing.md +314 -0
  109. package/plugins/ima-claude/skills/js-fp-wordpress/SKILL.md +301 -0
  110. package/plugins/ima-claude/skills/js-fp-wordpress/references/ajax-patterns.md +192 -0
  111. package/plugins/ima-claude/skills/js-fp-wordpress/references/event-patterns.md +136 -0
  112. package/plugins/ima-claude/skills/js-fp-wordpress/references/wp-integration.md +248 -0
  113. package/plugins/ima-claude/skills/livecanvas/SKILL.md +209 -0
  114. package/plugins/ima-claude/skills/livecanvas/references/livecanvas-features.md +311 -0
  115. package/plugins/ima-claude/skills/livecanvas/references/loops-and-logic.md +730 -0
  116. package/plugins/ima-claude/skills/livecanvas/references/picostrap.md +227 -0
  117. package/plugins/ima-claude/skills/mcp-atlassian/SKILL.md +339 -0
  118. package/plugins/ima-claude/skills/mcp-context7/SKILL.md +109 -0
  119. package/plugins/ima-claude/skills/mcp-memory/SKILL.md +182 -0
  120. package/plugins/ima-claude/skills/mcp-qdrant/SKILL.md +233 -0
  121. package/plugins/ima-claude/skills/mcp-sequential/SKILL.md +149 -0
  122. package/plugins/ima-claude/skills/mcp-serena/SKILL.md +174 -0
  123. package/plugins/ima-claude/skills/mcp-tavily/SKILL.md +118 -0
  124. package/plugins/ima-claude/skills/mcp-vestige/SKILL.md +259 -0
  125. package/plugins/ima-claude/skills/php-authnet/SKILL.md +275 -0
  126. package/plugins/ima-claude/skills/php-authnet/references/api-reference.md +624 -0
  127. package/plugins/ima-claude/skills/php-authnet/references/sandbox-testing.md +424 -0
  128. package/plugins/ima-claude/skills/php-fp/SKILL.md +333 -0
  129. package/plugins/ima-claude/skills/php-fp/examples/pure-functions.php +403 -0
  130. package/plugins/ima-claude/skills/php-fp/examples/tests/PureFunctionsTest.php +515 -0
  131. package/plugins/ima-claude/skills/php-fp/references/core-principles.md +277 -0
  132. package/plugins/ima-claude/skills/php-fp/references/testing-patterns.md +374 -0
  133. package/plugins/ima-claude/skills/php-fp-wordpress/SKILL.md +216 -0
  134. package/plugins/ima-claude/skills/php-fp-wordpress/references/fp-patterns.md +275 -0
  135. package/plugins/ima-claude/skills/php-fp-wordpress/references/plugin-architecture.md +295 -0
  136. package/plugins/ima-claude/skills/php-fp-wordpress/references/security-examples.md +203 -0
  137. package/plugins/ima-claude/skills/php-fp-wordpress/references/testing-strategy.md +259 -0
  138. package/plugins/ima-claude/skills/phpunit-wp/SKILL.md +716 -0
  139. package/plugins/ima-claude/skills/playwright/SKILL.md +434 -0
  140. package/plugins/ima-claude/skills/playwright/references/accessibility-testing.md +153 -0
  141. package/plugins/ima-claude/skills/playwright/references/ci-cd.md +268 -0
  142. package/plugins/ima-claude/skills/playwright/references/network-mocking.md +270 -0
  143. package/plugins/ima-claude/skills/playwright/references/visual-regression.md +215 -0
  144. package/plugins/ima-claude/skills/py-fp/SKILL.md +663 -0
  145. package/plugins/ima-claude/skills/py-fp/examples/pure-functions.py +185 -0
  146. package/plugins/ima-claude/skills/py-fp/examples/tests/test_pure_functions.py +244 -0
  147. package/plugins/ima-claude/skills/py-fp/references/core-principles.md +381 -0
  148. package/plugins/ima-claude/skills/py-fp/references/testing-patterns.md +283 -0
  149. package/plugins/ima-claude/skills/quasar-fp/SKILL.md +327 -0
  150. package/plugins/ima-claude/skills/quasar-fp/metadata.json +85 -0
  151. package/plugins/ima-claude/skills/quasar-fp/references/component-patterns.md +257 -0
  152. package/plugins/ima-claude/skills/quasar-fp/references/theme-integration.md +233 -0
  153. package/plugins/ima-claude/skills/quasar-fp/references/utility-classes.md +237 -0
  154. package/plugins/ima-claude/skills/quickstart/SKILL.md +129 -0
  155. package/plugins/ima-claude/skills/rails/SKILL.md +359 -0
  156. package/plugins/ima-claude/skills/resume-session/SKILL.md +68 -0
  157. package/plugins/ima-claude/skills/rg/SKILL.md +205 -0
  158. package/plugins/ima-claude/skills/ruby-fp/SKILL.md +336 -0
  159. package/plugins/ima-claude/skills/save-session/SKILL.md +81 -0
  160. package/plugins/ima-claude/skills/scorecard/SKILL.md +96 -0
  161. package/plugins/ima-claude/skills/skill-analyzer/SKILL.md +127 -0
  162. package/plugins/ima-claude/skills/skill-analyzer/references/advanced-checklist.md +44 -0
  163. package/plugins/ima-claude/skills/skill-analyzer/references/core-checklist.md +60 -0
  164. package/plugins/ima-claude/skills/skill-analyzer/scripts/analyze_skill.py +418 -0
  165. package/plugins/ima-claude/skills/skill-creator/LICENSE.txt +202 -0
  166. package/plugins/ima-claude/skills/skill-creator/SKILL.md +343 -0
  167. package/plugins/ima-claude/skills/skill-creator/references/output-patterns.md +82 -0
  168. package/plugins/ima-claude/skills/skill-creator/references/workflows.md +28 -0
  169. package/plugins/ima-claude/skills/skill-creator/scripts/init_skill.py +303 -0
  170. package/plugins/ima-claude/skills/skill-creator/scripts/package_skill.py +110 -0
  171. package/plugins/ima-claude/skills/skill-creator/scripts/quick_validate.py +103 -0
  172. package/plugins/ima-claude/skills/task-master/SKILL.md +51 -0
  173. package/plugins/ima-claude/skills/task-planner/SKILL.md +228 -0
  174. package/plugins/ima-claude/skills/task-runner/SKILL.md +192 -0
  175. package/plugins/ima-claude/skills/unit-testing/SKILL.md +198 -0
  176. package/plugins/ima-claude/skills/unit-testing/references/mock-patterns.md +181 -0
  177. package/plugins/ima-claude/skills/unit-testing/references/tdd-workflow.md +177 -0
  178. package/plugins/ima-claude/skills/unit-testing/references/test-strategy.md +126 -0
  179. package/plugins/ima-claude/skills/wp-local/SKILL.md +246 -0
  180. package/plugins/ima-claude/skills/wp-local/references/configuration.md +198 -0
  181. package/plugins/ima-claude/skills/wp-local/references/wp-cli-reference.md +406 -0
  182. package/plugins/ima-claude/skills/wp-local/scripts/wp-local.sh +61 -0
@@ -0,0 +1,277 @@
1
+ # PHP FP Core Principles
2
+
3
+ Deep-dive into functional programming philosophy adapted for PHP 8.1+.
4
+
5
+ ## The Three Laws
6
+
7
+ ### 1. Purity: Same Input, Same Output
8
+
9
+ ```php
10
+ <?php
11
+ declare(strict_types=1);
12
+
13
+ // PURE: Deterministic, no side effects
14
+ function calculateTax(float $amount, float $rate): float {
15
+ return $amount * $rate;
16
+ }
17
+
18
+ // IMPURE: Uses external state
19
+ function calculateTax(float $amount): float {
20
+ global $taxRate; // Hidden dependency
21
+ file_put_contents('/tmp/log', $amount); // Side effect
22
+ return $amount * $taxRate;
23
+ }
24
+ ```
25
+
26
+ **Test**: Can you call this function 1000 times with the same input and always get the same result without affecting anything else?
27
+
28
+ ### 2. Immutability: Never Mutate, Always Return New
29
+
30
+ ```php
31
+ <?php
32
+ declare(strict_types=1);
33
+
34
+ // MUTATES: Modifies input
35
+ function addTimestamp(array &$record): void {
36
+ $record['created_at'] = time();
37
+ }
38
+
39
+ // IMMUTABLE: Returns new array
40
+ function addTimestamp(array $record): array {
41
+ return [...$record, 'created_at' => time()];
42
+ }
43
+
44
+ // IMMUTABLE: Collection operations
45
+ function removeInactive(array $users): array {
46
+ return array_values(array_filter($users, fn($u) => $u['active']));
47
+ }
48
+
49
+ function updateById(array $items, int $id, array $updates): array {
50
+ return array_map(
51
+ fn($item) => $item['id'] === $id ? [...$item, ...$updates] : $item,
52
+ $items
53
+ );
54
+ }
55
+ ```
56
+
57
+ ### 3. Composition: Small Functions, Combined Simply
58
+
59
+ ```php
60
+ <?php
61
+ declare(strict_types=1);
62
+
63
+ // Build complex from simple - NO pipe() utility needed
64
+ function processOrder(array $order): array {
65
+ $validated = validateOrder($order);
66
+ if (!$validated['success']) return $validated;
67
+
68
+ $calculated = calculateTotals($validated['data']);
69
+ if (!$calculated['success']) return $calculated;
70
+
71
+ return applyDiscounts($calculated['data']);
72
+ }
73
+
74
+ // Each function is small, pure, testable
75
+ function validateOrder(array $order): array {
76
+ if (empty($order['items'])) {
77
+ return ['success' => false, 'error' => 'No items'];
78
+ }
79
+ return ['success' => true, 'data' => $order];
80
+ }
81
+ ```
82
+
83
+ ## PHP-Native Patterns
84
+
85
+ ### Closures as First-Class Functions
86
+
87
+ ```php
88
+ <?php
89
+ declare(strict_types=1);
90
+
91
+ // Factory returns configured closure
92
+ function createValidator(int $minLength): Closure {
93
+ return fn(string $value): bool => strlen($value) >= $minLength;
94
+ }
95
+
96
+ $validateName = createValidator(2);
97
+ $validateName('Jo'); // true
98
+
99
+ // Compose validators
100
+ function validateAll(array $validators): Closure {
101
+ return fn($value): bool => array_reduce(
102
+ $validators,
103
+ fn(bool $valid, callable $v) => $valid && $v($value),
104
+ true
105
+ );
106
+ }
107
+
108
+ $validateUsername = validateAll([
109
+ fn($v) => is_string($v),
110
+ fn($v) => strlen($v) >= 3,
111
+ fn($v) => strlen($v) <= 20,
112
+ fn($v) => preg_match('/^[a-z0-9_]+$/', $v) === 1,
113
+ ]);
114
+ ```
115
+
116
+ ### Match Expressions for Branching
117
+
118
+ ```php
119
+ <?php
120
+ declare(strict_types=1);
121
+
122
+ // Pure branching with match
123
+ function getStatusMessage(string $status): string {
124
+ return match($status) {
125
+ 'pending' => 'Awaiting review',
126
+ 'approved' => 'Ready to proceed',
127
+ 'rejected' => 'Request denied',
128
+ 'cancelled' => 'User cancelled',
129
+ default => 'Unknown status',
130
+ };
131
+ }
132
+
133
+ // Type-safe dispatch
134
+ function processEvent(array $event): array {
135
+ return match($event['type']) {
136
+ 'user.created' => handleUserCreated($event['data']),
137
+ 'user.updated' => handleUserUpdated($event['data']),
138
+ 'user.deleted' => handleUserDeleted($event['data']),
139
+ default => ['handled' => false, 'reason' => 'Unknown event'],
140
+ };
141
+ }
142
+ ```
143
+
144
+ ### Readonly Properties for Immutable Objects
145
+
146
+ ```php
147
+ <?php
148
+ declare(strict_types=1);
149
+
150
+ // Value objects with readonly (PHP 8.1+)
151
+ readonly class Money {
152
+ public function __construct(
153
+ public int $amount,
154
+ public string $currency
155
+ ) {}
156
+
157
+ public function add(Money $other): Money {
158
+ if ($this->currency !== $other->currency) {
159
+ throw new InvalidArgumentException('Currency mismatch');
160
+ }
161
+ return new Money($this->amount + $other->amount, $this->currency);
162
+ }
163
+ }
164
+
165
+ // Usage - immutable by design
166
+ $price = new Money(1000, 'USD');
167
+ $tax = new Money(100, 'USD');
168
+ $total = $price->add($tax); // New Money object
169
+ ```
170
+
171
+ ## Anti-Pattern Recognition
172
+
173
+ ### Hidden Mutation
174
+
175
+ ```php
176
+ <?php
177
+ // ANTI-PATTERN: Mutation disguised as transformation
178
+ function processUser(array $user): array {
179
+ $user['processed_at'] = time(); // Mutates if passed by reference
180
+ return validateUser($user);
181
+ }
182
+
183
+ // CORRECT: Explicit new array
184
+ function processUser(array $user): array {
185
+ return validateUser([
186
+ ...$user,
187
+ 'processed_at' => time()
188
+ ]);
189
+ }
190
+ ```
191
+
192
+ ### Configuration in Hot Paths
193
+
194
+ ```php
195
+ <?php
196
+ // ANTI-PATTERN: O(records x fields) - schema accessed every iteration
197
+ function mapRecords(array $records, array $schema): array {
198
+ return array_map(function($record) use ($schema) {
199
+ return array_combine(
200
+ array_column($schema['fields'], 'name'),
201
+ array_map(fn($f) => $record[$f['key']], $schema['fields'])
202
+ );
203
+ }, $records);
204
+ }
205
+
206
+ // CORRECT: Pre-compile, then execute - O(records + fields)
207
+ function createRecordMapper(array $schema): Closure {
208
+ $fields = $schema['fields'];
209
+ $names = array_column($fields, 'name');
210
+ $keys = array_column($fields, 'key');
211
+
212
+ return fn(array $record): array => array_combine(
213
+ $names,
214
+ array_map(fn($key) => $record[$key], $keys)
215
+ );
216
+ }
217
+
218
+ $mapper = createRecordMapper($schema); // Setup once
219
+ $results = array_map($mapper, $records); // Linear execution
220
+ ```
221
+
222
+ ### Over-Engineering Validation
223
+
224
+ ```php
225
+ <?php
226
+ // ANTI-PATTERN: Complex machinery for simple check
227
+ $emailValidator = (new ValidatorBuilder())
228
+ ->addRule(new FormatRule(new EmailPattern()))
229
+ ->addRule(new LengthRule(new Range(5, 254)))
230
+ ->addTransformer(new LowercaseTransformer())
231
+ ->build();
232
+
233
+ // CORRECT: Simple, direct, readable
234
+ function validateEmail(string $email): bool {
235
+ $normalized = strtolower(trim($email));
236
+ return strlen($normalized) >= 5
237
+ && strlen($normalized) <= 254
238
+ && filter_var($normalized, FILTER_VALIDATE_EMAIL) !== false;
239
+ }
240
+ ```
241
+
242
+ ## Result Type Pattern
243
+
244
+ Consistent error handling without exceptions for expected failures.
245
+
246
+ ```php
247
+ <?php
248
+ declare(strict_types=1);
249
+
250
+ // Standard result shape
251
+ function success(mixed $data): array {
252
+ return ['success' => true, 'data' => $data];
253
+ }
254
+
255
+ function failure(string $error, array $context = []): array {
256
+ return ['success' => false, 'error' => $error, 'context' => $context];
257
+ }
258
+
259
+ // Usage in pure functions
260
+ function parseJson(string $json): array {
261
+ $data = json_decode($json, true);
262
+ return json_last_error() === JSON_ERROR_NONE
263
+ ? success($data)
264
+ : failure('Invalid JSON', ['error' => json_last_error_msg()]);
265
+ }
266
+
267
+ // Chain results
268
+ function processInput(string $json): array {
269
+ $parsed = parseJson($json);
270
+ if (!$parsed['success']) return $parsed;
271
+
272
+ $validated = validateData($parsed['data']);
273
+ if (!$validated['success']) return $validated;
274
+
275
+ return transformData($validated['data']);
276
+ }
277
+ ```
@@ -0,0 +1,374 @@
1
+ # PHP FP Testing Patterns
2
+
3
+ Comprehensive testing philosophy for pure functions using PHPUnit.
4
+
5
+ ## Why Pure Functions Enable Better Testing
6
+
7
+ Pure functions guarantee:
8
+ - **Deterministic**: Same input always produces same output
9
+ - **Isolated**: No side effects means tests never interfere
10
+ - **Fast**: No I/O, network, or database needed
11
+ - **Complete**: All edge cases can be systematically tested
12
+
13
+ ## The Test Matrix Approach
14
+
15
+ ### Systematic Type Coverage
16
+
17
+ ```php
18
+ <?php
19
+ declare(strict_types=1);
20
+
21
+ use PHPUnit\Framework\TestCase;
22
+ use PHPUnit\Framework\Attributes\DataProvider;
23
+
24
+ class ValidatorTest extends TestCase
25
+ {
26
+ // Test expected use cases
27
+ #[DataProvider('validInputProvider')]
28
+ public function testAcceptsValidInput(mixed $input, bool $expected): void
29
+ {
30
+ $this->assertSame($expected, validatePositiveInteger($input));
31
+ }
32
+
33
+ public static function validInputProvider(): array
34
+ {
35
+ return [
36
+ 'positive integer' => [5, true],
37
+ 'large number' => [1000000, true],
38
+ 'boundary: 1' => [1, true],
39
+ 'zero' => [0, false],
40
+ 'negative' => [-5, false],
41
+ ];
42
+ }
43
+
44
+ // Test ALL PHP types systematically
45
+ #[DataProvider('invalidTypeProvider')]
46
+ public function testRejectsInvalidTypes(mixed $input): void
47
+ {
48
+ $this->expectException(TypeError::class);
49
+ validatePositiveInteger($input);
50
+ }
51
+
52
+ public static function invalidTypeProvider(): array
53
+ {
54
+ return [
55
+ 'null' => [null],
56
+ 'string' => ['5'],
57
+ 'float' => [5.0],
58
+ 'bool true' => [true],
59
+ 'bool false' => [false],
60
+ 'empty array' => [[]],
61
+ 'array with values' => [[1, 2, 3]],
62
+ 'object' => [new stdClass()],
63
+ 'closure' => [fn() => 5],
64
+ ];
65
+ }
66
+ }
67
+ ```
68
+
69
+ ### Boundary Value Testing
70
+
71
+ ```php
72
+ <?php
73
+ declare(strict_types=1);
74
+
75
+ class RangeValidatorTest extends TestCase
76
+ {
77
+ private Closure $validator;
78
+
79
+ protected function setUp(): void
80
+ {
81
+ $this->validator = createRangeValidator(min: 0, max: 100);
82
+ }
83
+
84
+ #[DataProvider('boundaryProvider')]
85
+ public function testBoundaryValues(int $value, bool $expected): void
86
+ {
87
+ $this->assertSame($expected, ($this->validator)($value));
88
+ }
89
+
90
+ public static function boundaryProvider(): array
91
+ {
92
+ return [
93
+ 'below min' => [-1, false],
94
+ 'at min' => [0, true],
95
+ 'above min' => [1, true],
96
+ 'middle' => [50, true],
97
+ 'below max' => [99, true],
98
+ 'at max' => [100, true],
99
+ 'above max' => [101, false],
100
+ ];
101
+ }
102
+ }
103
+ ```
104
+
105
+ ## Testing Composed Functions
106
+
107
+ ### Test Components Independently
108
+
109
+ ```php
110
+ <?php
111
+ declare(strict_types=1);
112
+
113
+ // Functions under test
114
+ function normalizeEmail(string $email): string {
115
+ return strtolower(trim($email));
116
+ }
117
+
118
+ function validateEmailFormat(string $email): bool {
119
+ return filter_var($email, FILTER_VALIDATE_EMAIL) !== false;
120
+ }
121
+
122
+ function validateEmailDomain(string $email, array $blocked): bool {
123
+ $domain = substr($email, strpos($email, '@') + 1);
124
+ return !in_array($domain, $blocked, true);
125
+ }
126
+
127
+ // Test each component
128
+ class EmailValidationTest extends TestCase
129
+ {
130
+ public function testNormalizeEmail(): void
131
+ {
132
+ $this->assertSame('test@example.com', normalizeEmail(' TEST@EXAMPLE.COM '));
133
+ }
134
+
135
+ #[DataProvider('formatProvider')]
136
+ public function testValidateEmailFormat(string $email, bool $valid): void
137
+ {
138
+ $this->assertSame($valid, validateEmailFormat($email));
139
+ }
140
+
141
+ public static function formatProvider(): array
142
+ {
143
+ return [
144
+ 'valid' => ['test@example.com', true],
145
+ 'no @' => ['testexample.com', false],
146
+ 'no domain' => ['test@', false],
147
+ 'no local' => ['@example.com', false],
148
+ 'double @' => ['test@@example.com', false],
149
+ ];
150
+ }
151
+
152
+ public function testValidateEmailDomain(): void
153
+ {
154
+ $blocked = ['spam.com', 'temp.net'];
155
+
156
+ $this->assertTrue(validateEmailDomain('user@example.com', $blocked));
157
+ $this->assertFalse(validateEmailDomain('user@spam.com', $blocked));
158
+ }
159
+ }
160
+ ```
161
+
162
+ ### Test the Composition
163
+
164
+ ```php
165
+ <?php
166
+ declare(strict_types=1);
167
+
168
+ class EmailValidationIntegrationTest extends TestCase
169
+ {
170
+ private Closure $validateEmail;
171
+
172
+ protected function setUp(): void
173
+ {
174
+ $blocked = ['spam.com'];
175
+ $this->validateEmail = createEmailValidator($blocked);
176
+ }
177
+
178
+ #[DataProvider('integrationProvider')]
179
+ public function testFullValidation(string $input, array $expected): void
180
+ {
181
+ $result = ($this->validateEmail)($input);
182
+ $this->assertSame($expected['success'], $result['success']);
183
+ if (!$expected['success']) {
184
+ $this->assertStringContainsString($expected['error'], $result['error']);
185
+ }
186
+ }
187
+
188
+ public static function integrationProvider(): array
189
+ {
190
+ return [
191
+ 'valid email' => [
192
+ 'USER@EXAMPLE.COM',
193
+ ['success' => true]
194
+ ],
195
+ 'invalid format' => [
196
+ 'not-an-email',
197
+ ['success' => false, 'error' => 'format']
198
+ ],
199
+ 'blocked domain' => [
200
+ 'user@spam.com',
201
+ ['success' => false, 'error' => 'blocked']
202
+ ],
203
+ ];
204
+ }
205
+ }
206
+ ```
207
+
208
+ ## Testing Function Factories
209
+
210
+ ```php
211
+ <?php
212
+ declare(strict_types=1);
213
+
214
+ class FunctionFactoryTest extends TestCase
215
+ {
216
+ public function testCreateValidatorReturnsCallable(): void
217
+ {
218
+ $validator = createValidator(minLength: 5);
219
+ $this->assertIsCallable($validator);
220
+ }
221
+
222
+ public function testFactoryConfigurationIsCaptured(): void
223
+ {
224
+ $validator5 = createValidator(minLength: 5);
225
+ $validator10 = createValidator(minLength: 10);
226
+
227
+ // Same input, different results based on configuration
228
+ $this->assertTrue($validator5('hello')); // 5 >= 5
229
+ $this->assertFalse($validator10('hello')); // 5 < 10
230
+ }
231
+
232
+ public function testFactoryProducesIndependentInstances(): void
233
+ {
234
+ $v1 = createValidator(minLength: 3);
235
+ $v2 = createValidator(minLength: 3);
236
+
237
+ // Different instances, same behavior
238
+ $this->assertNotSame($v1, $v2);
239
+ $this->assertSame($v1('abc'), $v2('abc'));
240
+ }
241
+ }
242
+ ```
243
+
244
+ ## Result Type Testing
245
+
246
+ ```php
247
+ <?php
248
+ declare(strict_types=1);
249
+
250
+ class ResultTypeTest extends TestCase
251
+ {
252
+ public function testSuccessResultStructure(): void
253
+ {
254
+ $result = divide(10.0, 2.0);
255
+
256
+ $this->assertArrayHasKey('success', $result);
257
+ $this->assertTrue($result['success']);
258
+ $this->assertArrayHasKey('data', $result);
259
+ $this->assertSame(5.0, $result['data']);
260
+ }
261
+
262
+ public function testFailureResultStructure(): void
263
+ {
264
+ $result = divide(10.0, 0.0);
265
+
266
+ $this->assertArrayHasKey('success', $result);
267
+ $this->assertFalse($result['success']);
268
+ $this->assertArrayHasKey('error', $result);
269
+ $this->assertIsString($result['error']);
270
+ }
271
+
272
+ public function testResultChaining(): void
273
+ {
274
+ // Test that failures short-circuit
275
+ $result = processCalculation(10.0, 0.0, 5.0);
276
+
277
+ $this->assertFalse($result['success']);
278
+ $this->assertStringContainsString('zero', $result['error']);
279
+ }
280
+ }
281
+ ```
282
+
283
+ ## Mocking Dependencies
284
+
285
+ ```php
286
+ <?php
287
+ declare(strict_types=1);
288
+
289
+ class ServiceWithDependenciesTest extends TestCase
290
+ {
291
+ public function testUserServiceWithMocks(): void
292
+ {
293
+ // Create mock implementations
294
+ $mockHasher = $this->createMock(PasswordHasherInterface::class);
295
+ $mockHasher->method('hash')->willReturn('hashed_password');
296
+
297
+ $mockDb = $this->createMock(DatabaseInterface::class);
298
+ $mockDb->method('save')->willReturn(['id' => 1, 'name' => 'Test']);
299
+
300
+ // Inject mocks - DI makes this trivial
301
+ $result = saveUser(
302
+ ['name' => 'Test', 'password' => 'secret'],
303
+ $mockHasher,
304
+ $mockDb
305
+ );
306
+
307
+ $this->assertSame(1, $result['id']);
308
+ }
309
+
310
+ public function testPureFunctionNeedsNoMocks(): void
311
+ {
312
+ // Pure functions are trivially testable
313
+ $result = calculateDiscount(100.0, 0.1);
314
+ $this->assertSame(90.0, $result);
315
+ // No setup, no mocks, no cleanup
316
+ }
317
+ }
318
+ ```
319
+
320
+ ## Performance Testing Pure Functions
321
+
322
+ ```php
323
+ <?php
324
+ declare(strict_types=1);
325
+
326
+ class PerformanceTest extends TestCase
327
+ {
328
+ public function testFactoryPerformanceGain(): void
329
+ {
330
+ $data = range(1, 10000);
331
+ $config = ['multiplier' => 2];
332
+
333
+ // Without factory: config accessed each iteration
334
+ $start = microtime(true);
335
+ $results1 = array_map(
336
+ fn($n) => $n * $config['multiplier'],
337
+ $data
338
+ );
339
+ $withoutFactory = microtime(true) - $start;
340
+
341
+ // With factory: config captured once
342
+ $processor = fn($n) => $n * 2; // Config pre-compiled
343
+ $start = microtime(true);
344
+ $results2 = array_map($processor, $data);
345
+ $withFactory = microtime(true) - $start;
346
+
347
+ $this->assertSame($results1, $results2);
348
+ // Factory should be faster (config not re-accessed)
349
+ }
350
+ }
351
+ ```
352
+
353
+ ## Test Organization
354
+
355
+ ```
356
+ tests/
357
+ Unit/
358
+ Validators/
359
+ EmailValidatorTest.php
360
+ RangeValidatorTest.php
361
+ Transformers/
362
+ DataMapperTest.php
363
+ Utils/
364
+ ResultTypeTest.php
365
+ Integration/
366
+ ValidationPipelineTest.php
367
+ ```
368
+
369
+ **Key Principles**:
370
+ 1. One test class per function/factory
371
+ 2. Data providers for systematic coverage
372
+ 3. Test components independently, then composition
373
+ 4. Pure functions need no mocks
374
+ 5. Edge cases are cheap to test - test them all