prisma-php 0.0.1

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.
@@ -0,0 +1,637 @@
1
+ ---
2
+ title: Fetching Data
3
+ description: Learn how to fetch data in Prisma PHP using `pp.fetchFunction`, `#[Exposed]`, page handlers, and route handlers.
4
+ related:
5
+ title: API Reference
6
+ description: Learn more about the features mentioned in this page by reading the Prisma PHP docs.
7
+ links:
8
+ - /docs/fetch-function
9
+ - /docs/index-php
10
+ - /docs/route-php
11
+ - /docs/pages-and-layouts
12
+ ---
13
+
14
+ This page will walk you through how you can fetch data in Prisma PHP, when to use `pp.fetchFunction(...)`, when to use `index.php`, when to use `route.php`, and how to validate incoming data correctly on the PHP side.
15
+
16
+ Prisma PHP has a different mental model from Next.js. Instead of focusing on React Server Components, Suspense streaming, and client data libraries, Prisma PHP gives you direct function invocation from the frontend to PHP, plus file-based routing for page UI and direct route handlers.
17
+
18
+ When using `pp.fetchFunction(...)`, the PHP function must be explicitly exposed with `#[Exposed]`. Functions are private by default, so a non-exposed function cannot be called from the client.
19
+
20
+ For validation, the default backend pattern should be **`PP\Validator`**. Use it to sanitize, cast, and validate values inside exposed functions and route handlers. This keeps the browser reactive while keeping the authoritative validation on the server.
21
+
22
+ ## Validation is a server concern
23
+
24
+ In Prisma PHP, frontend interactivity and backend validation work together, but they are not the same thing.
25
+
26
+ Use this split:
27
+
28
+ - use **PulsePoint** for local input state, loading state, and reactive UI feedback
29
+ - use **`pp.fetchFunction(...)`** to send data to PHP during interactive flows
30
+ - use **`PP\Validator`** in PHP to sanitize, cast, and validate the incoming payload
31
+ - return a **structured response** such as `success`, `errors`, and normalized values
32
+ - do not trust browser-only validation as the final source of truth
33
+
34
+ This makes `Validator` a natural fit for:
35
+
36
+ - live field validation
37
+ - inline availability checks
38
+ - form submission validation
39
+ - normalization before database writes
40
+ - reusable backend validation for both page-local RPC and direct handlers
41
+
42
+ ## Recommended Validator pattern for `pp.fetchFunction(...)`
43
+
44
+ For reactive UI work, the best Prisma PHP pattern is usually:
45
+
46
+ 1. keep the current field state in PulsePoint
47
+ 2. call an exposed PHP function with `pp.fetchFunction(...)`
48
+ 3. validate on the server with `Validator`
49
+ 4. return a structured object for the UI to render
50
+
51
+ The official Validator docs recommend the **`Rule` builder** for most rule-based validation because it is easier to discover, easier to refactor, and less error-prone than raw pipe strings.
52
+
53
+ ## Recommended pattern for reactive Prisma PHP UIs
54
+
55
+ For normal interactive UI work in Prisma PHP, the default approach should be:
56
+
57
+ 1. render the page with `index.php`
58
+ 2. manage browser-side reactivity with PulsePoint
59
+ 3. call PHP from the frontend with `pp.fetchFunction(...)`
60
+ 4. expose callable functions with `#[Exposed]`
61
+
62
+ This is the preferred pattern for live search, inline validation, toggles, quick edits, filtering, lightweight mutations, and similar route-local interactions.
63
+
64
+ Do **not** default to building extra `route.php` endpoints for every interactive action when `pp.fetchFunction(...)` is the better fit. Reserve `route.php` for explicit API-style endpoints, webhooks, JSON handlers, or routes that must be called independently of the page.
65
+
66
+ ## Fetching data in Prisma PHP
67
+
68
+ There are three common ways to fetch or load data in Prisma PHP:
69
+
70
+ 1. **Load data directly in `index.php`** for full-stack page rendering
71
+ 2. **Call PHP functions from JavaScript with `pp.fetchFunction(...)`**
72
+ 3. **Use `route.php`** for API-style or direct JSON endpoints
73
+
74
+ ### Choosing the right approach
75
+
76
+ Use this rule of thumb:
77
+
78
+ - use `index.php` when the route should render page UI
79
+ - use `pp.fetchFunction(...)` when frontend interactivity should call backend PHP directly without creating a dedicated route file
80
+ - use `route.php` when you need a direct API-style endpoint, JSON handler, webhook, or no-view request path
81
+
82
+ In a full-stack Prisma PHP project, normal page data is usually loaded in `index.php`, and interactive frontend updates are commonly handled with `pp.fetchFunction(...)`.
83
+
84
+ ## Fetching data in `index.php`
85
+
86
+ For full-stack page routes, the most direct way to fetch data is inside `index.php`.
87
+
88
+ Because `index.php` is the UI entry point for a route, it is the right place to query the database, prepare data, and render HTML or PHPX-based output.
89
+
90
+ ```php filename="src/app/blog/index.php"
91
+ <?php
92
+
93
+ use Lib\Prisma\Classes\Prisma;
94
+
95
+ $prisma = Prisma::getInstance();
96
+
97
+ $posts = $prisma->post->findMany([
98
+ 'orderBy' => [
99
+ 'createdAt' => 'desc',
100
+ ],
101
+ ]);
102
+
103
+ ?>
104
+
105
+ <ul>
106
+ <?php foreach ($posts as $post): ?>
107
+ <li><?= htmlspecialchars($post['title']) ?></li>
108
+ <?php endforeach; ?>
109
+ </ul>
110
+ ```
111
+
112
+ This is the closest Prisma PHP equivalent to server-side page data loading.
113
+
114
+ ## Fetching data with `pp.fetchFunction(...)`
115
+
116
+ One of Prisma PHP’s main frontend-to-backend patterns is **Direct Function Invocation** with `pp.fetchFunction(...)`.
117
+
118
+ This allows you to call PHP functions directly from frontend JavaScript without creating route files, controllers, or custom API endpoints.
119
+
120
+ ### Basic example
121
+
122
+ Before a PHP function can be called with `pp.fetchFunction(...)`, it must be marked with `#[Exposed]`.
123
+
124
+ Functions and methods are private by default in this flow. `pp.fetchFunction(...)` will only work for functions that are intentionally exposed.
125
+
126
+ ```php filename="src/app/users/index.php"
127
+ <?php
128
+
129
+ use PP\Attributes\Exposed;
130
+ use PP\Validator;
131
+
132
+ #[Exposed]
133
+ function validateUser($data)
134
+ {
135
+ $id = Validator::int($data->id);
136
+
137
+ if ($id <= 0) {
138
+ return [
139
+ 'success' => false,
140
+ 'msg' => 'Invalid ID',
141
+ ];
142
+ }
143
+
144
+ return [
145
+ 'success' => true,
146
+ 'msg' => 'Valid ID',
147
+ ];
148
+ }
149
+ ?>
150
+
151
+ <script>
152
+ const [id, setId] = pp.state('');
153
+ const [msg, setMsg] = pp.state('');
154
+
155
+ async function checkId() {
156
+ const response = await pp.fetchFunction('validateUser', { id });
157
+
158
+ if (response.success) {
159
+ setMsg(response.msg);
160
+ } else {
161
+ setMsg(response.msg);
162
+ }
163
+ }
164
+ </script>
165
+
166
+ <input value="{id}" oninput="setId(val)" />
167
+ <button onclick="checkId()">Check</button>
168
+ <p>{msg}</p>
169
+ ```
170
+
171
+ If `validateUser()` is not decorated with `#[Exposed]`, the client cannot invoke it through `pp.fetchFunction(...)`.
172
+ Prisma PHP automatically serializes the request and parses the PHP return value back into JavaScript. If the response is JSON-compatible, the result is returned as a parsed object. If the backend returns non-JSON data, the result may be returned as a raw string.
173
+
174
+ ## Why `pp.fetchFunction(...)` is important
175
+
176
+ `pp.fetchFunction(...)` is one of Prisma PHP’s biggest differences from other frameworks.
177
+
178
+ It removes the need for:
179
+
180
+ - controller boilerplate
181
+ - custom route definitions
182
+ - manual `fetch('/api/...')` setup for many interactions
183
+ - repetitive JSON parsing code on both sides
184
+
185
+ Instead, you define an exposed PHP function and call it directly.
186
+
187
+ This makes it especially useful for:
188
+
189
+ - form validation
190
+ - search-as-you-type
191
+ - quick data checks
192
+ - lightweight mutations
193
+ - UI-triggered backend actions
194
+ - interactive dashboard behavior
195
+
196
+ ## Working with `Exposed`
197
+
198
+ `#[Exposed]` is not just an optional enhancement for `pp.fetchFunction(...)`—it is the requirement that makes a PHP function callable from the client.
199
+
200
+ Functions are private by default. To allow frontend access, you must explicitly opt in with the `Exposed` attribute. This keeps server logic locked down unless you intentionally expose it.
201
+
202
+ ```php filename="src/app/users/index.php"
203
+ <?php
204
+
205
+ use PP\Attributes\Exposed;
206
+ use PP\Validator;
207
+
208
+ #[Exposed]
209
+ function validateUser($data)
210
+ {
211
+ $id = Validator::int($data->id);
212
+
213
+ return [
214
+ 'success' => $id > 0,
215
+ ];
216
+ }
217
+
218
+ #[Exposed(
219
+ requiresAuth: true,
220
+ allowedRoles: ['admin'],
221
+ limits: '20/min'
222
+ )]
223
+ function searchUsers($data)
224
+ {
225
+ $query = Validator::string($data->query ?? '');
226
+
227
+ return [
228
+ 'success' => true,
229
+ 'query' => $query,
230
+ ];
231
+ }
232
+ ```
233
+
234
+ The `Exposed` attribute supports:
235
+
236
+ - `requiresAuth`
237
+ - `allowedRoles`
238
+ - `limits`
239
+
240
+ Based on the provided class definition, it can be applied to functions and methods and is designed for direct invocation with authentication, role checks, and rate limiting.
241
+
242
+ ### What each option does
243
+
244
+ - `#[Exposed]` makes the function publicly callable from `pp.fetchFunction(...)`
245
+ - `requiresAuth: true` restricts access to authenticated users
246
+ - `allowedRoles: [...]` restricts access to specific roles
247
+ - `limits` applies one or more rate limits such as `"5/minute"` or `["5/m", "100/d"]`
248
+
249
+ ### Private by default
250
+
251
+ This is the key rule to remember:
252
+
253
+ - `pp.fetchFunction('myFunction')` only works if `myFunction` is exposed
254
+ - a normal PHP function without `#[Exposed]` remains private to the server context
255
+ - use `#[Exposed]` whenever you want frontend JavaScript to call a PHP function directly
256
+
257
+ ## Parameters and automatic parsing
258
+
259
+ `pp.fetchFunction(...)` accepts the function name, a payload object, and an optional third argument for request behavior.
260
+
261
+ ```ts
262
+ fetchFunction<T = any>(
263
+ functionName: string,
264
+ data: Record<string, any> = {},
265
+ options: boolean | RpcOptions = false
266
+ ): Promise<T | void>
267
+ ```
268
+
269
+ ### Parameters
270
+
271
+ - `functionName`: the exact PHP function name to call
272
+ - `data`: an object containing the arguments to send
273
+ - `options`: either `true`/`false` for simple abort behavior or an options object for advanced features
274
+
275
+ ### Common options
276
+
277
+ - `abortPrevious`: cancel previous pending requests from the same caller
278
+ - `onStream`: handle streamed SSE chunks
279
+ - `onStreamError`: handle stream errors
280
+ - `onStreamComplete`: run logic when streaming finishes
281
+ - `onUploadProgress`: track real upload progress when sending `File` or `FileList`
282
+ - `onUploadComplete`: run logic when the upload finishes
283
+
284
+ This is useful for search inputs, streaming responses, uploads, and other rapid repeated interactions.
285
+
286
+ ```html
287
+ <script>
288
+ async function onSearch(val) {
289
+ const response = await pp.fetchFunction(
290
+ "searchUsers",
291
+ { query: val },
292
+ { abortPrevious: true },
293
+ );
294
+
295
+ console.log(response);
296
+ }
297
+ </script>
298
+ ```
299
+
300
+ As with every `pp.fetchFunction(...)` call, `searchUsers` must be decorated with `#[Exposed]` on the PHP side.
301
+
302
+ ## File upload support
303
+
304
+ Prisma PHP’s fetch function automatically switches to multipart handling when the payload contains a `File` or `FileList`.
305
+
306
+ The upload handler still needs to be exposed with `#[Exposed]` if it is being called from `pp.fetchFunction(...)`.
307
+
308
+ That means you do not need to manually build `FormData` for many common upload flows.
309
+
310
+ ```html
311
+ <script>
312
+ async function uploadAvatar() {
313
+ const fileInput = document.getElementById("avatar");
314
+
315
+ const response = await pp.fetchFunction("uploadAvatar", {
316
+ image: fileInput.files[0],
317
+ userId: 12,
318
+ });
319
+
320
+ console.log(response);
321
+ }
322
+ </script>
323
+
324
+ <input id="avatar" type="file" />
325
+ <button onclick="uploadAvatar()">Upload</button>
326
+ ```
327
+
328
+ ## Request cancellation
329
+
330
+ For fast-changing inputs such as live search, Prisma PHP can automatically cancel stale requests by using `abortPrevious: true`.
331
+
332
+ ```html
333
+ <script>
334
+ async function searchPosts(val) {
335
+ const response = await pp.fetchFunction(
336
+ "searchPosts",
337
+ { query: val },
338
+ { abortPrevious: true },
339
+ );
340
+
341
+ console.log(response);
342
+ }
343
+ </script>
344
+ ```
345
+
346
+ This helps prevent race conditions and outdated responses from overwriting newer UI state.
347
+
348
+ ## Transparent redirects
349
+
350
+ If a PHP function returns a redirect instruction, or if authentication fails in a way that triggers redirect behavior, `pp.fetchFunction(...)` can automatically perform a client-side redirect through Prisma PHP’s navigation helpers.
351
+
352
+ This makes interactive authenticated flows smoother without requiring extra frontend redirect wiring.
353
+
354
+ ## Using `Validator` inside exposed functions
355
+
356
+ An exposed function should do more than simply accept the raw payload. It should:
357
+
358
+ 1. normalize incoming values
359
+ 2. validate the shape and business rules
360
+ 3. return predictable frontend-friendly output
361
+
362
+ A practical pattern is:
363
+
364
+ ```php filename="src/app/settings/index.php"
365
+ <?php
366
+
367
+ use PP\Attributes\Exposed;
368
+ use PP\Rule;
369
+ use PP\Validator;
370
+
371
+ #[Exposed]
372
+ function saveProfile($data)
373
+ {
374
+ $name = Validator::string($data->name ?? '');
375
+ $email = Validator::email($data->email ?? '');
376
+ $newsletter = Validator::boolean($data->newsletter ?? false);
377
+
378
+ $nameResult = Validator::withRules($name, Rule::required()->min(3)->max(80));
379
+
380
+ if ($nameResult !== true) {
381
+ return [
382
+ 'success' => false,
383
+ 'errors' => [
384
+ 'name' => $nameResult,
385
+ ],
386
+ ];
387
+ }
388
+
389
+ if ($email === null) {
390
+ return [
391
+ 'success' => false,
392
+ 'errors' => [
393
+ 'email' => 'A valid email address is required.',
394
+ ],
395
+ ];
396
+ }
397
+
398
+ return [
399
+ 'success' => true,
400
+ 'errors' => [],
401
+ 'data' => [
402
+ 'name' => $name,
403
+ 'email' => $email,
404
+ 'newsletter' => $newsletter ?? false,
405
+ ],
406
+ ];
407
+ }
408
+ ```
409
+
410
+ This pattern works well because the UI can render field errors immediately, while the backend still controls the final truth.
411
+
412
+ ## `Validator` helpers you will commonly use
413
+
414
+ Some of the most useful helpers for reactive and backend validation include:
415
+
416
+ - `Validator::string(...)` for sanitized strings
417
+ - `Validator::email(...)` for email addresses
418
+ - `Validator::int(...)` and `Validator::float(...)` for numeric casting
419
+ - `Validator::boolean(...)` for smart boolean conversion
420
+ - `Validator::json(...)` for JSON-safe validation or encoding
421
+ - `Validator::date(...)` and `Validator::dateTime(...)` for normalized date values
422
+ - `Validator::enum(...)` and `Validator::enumClass(...)` for allowed options
423
+ - `Validator::withRules(...)` for rule-based validation
424
+
425
+ Use primitive helpers for normalization first, then apply `withRules(...)` when the value has business constraints such as required, minimum length, confirmed fields, allowed lists, or date rules.
426
+
427
+ ## Using `route.php` for data fetching
428
+
429
+ When you need an explicit endpoint instead of direct function invocation, use `route.php`.
430
+
431
+ This is a better fit for:
432
+
433
+ - external integrations
434
+ - webhooks
435
+ - standalone JSON endpoints
436
+ - routes that should be called outside the current page context
437
+ - API-style request handlers
438
+
439
+ Example:
440
+
441
+ ```php filename="src/app/users/route.php"
442
+ <?php
443
+
444
+ use Lib\Prisma\Classes\Prisma;
445
+ use PP\Request;
446
+
447
+ if (!Request::$isGet) {
448
+ exit;
449
+ }
450
+
451
+ $prisma = Prisma::getInstance();
452
+
453
+ $users = $prisma->user->findMany();
454
+
455
+ echo json_encode($users);
456
+ ```
457
+
458
+ In Prisma PHP, `route.php` is the direct handler entry point for a route segment and is commonly used for API-style behavior.
459
+
460
+ ## `index.php` vs `route.php` for fetching data
461
+
462
+ Use `index.php` when:
463
+
464
+ - the route should render page UI
465
+ - data belongs to the page render itself
466
+ - the request is part of the normal full-stack page flow
467
+
468
+ Use `route.php` when:
469
+
470
+ - the route should act like an API endpoint
471
+ - the response should bypass standard page rendering
472
+ - the request needs a dedicated direct handler
473
+
474
+ Use `pp.fetchFunction(...)` when:
475
+
476
+ - you are inside an existing page
477
+ - the frontend needs to call PHP directly
478
+ - you want less boilerplate than a dedicated route file
479
+
480
+ ## Common Prisma PHP data-fetching patterns
481
+
482
+ ### Page loads in `index.php`
483
+
484
+ ```php filename="src/app/dashboard/index.php"
485
+ <?php
486
+
487
+ use Lib\Prisma\Classes\Prisma;
488
+
489
+ $prisma = Prisma::getInstance();
490
+
491
+ $stats = $prisma->user->count();
492
+
493
+ echo "<h1>Total users: " . (int) $stats . "</h1>";
494
+ ```
495
+
496
+ ### Interactive validation with `pp.fetchFunction(...)`
497
+
498
+ ```php filename="src/app/profile/index.php"
499
+ <?php
500
+
501
+ use PP\Attributes\Exposed;
502
+ use PP\Rule;
503
+ use PP\Validator;
504
+
505
+ #[Exposed]
506
+ function validateUsername($data)
507
+ {
508
+ $username = Validator::string($data->username ?? '');
509
+
510
+ $result = Validator::withRules(
511
+ $username,
512
+ Rule::required()->min(3)->max(30)
513
+ );
514
+
515
+ if ($result !== true) {
516
+ return [
517
+ 'success' => false,
518
+ 'errors' => [
519
+ 'username' => $result,
520
+ ],
521
+ ];
522
+ }
523
+
524
+ if ($username === 'admin') {
525
+ return [
526
+ 'success' => false,
527
+ 'errors' => [
528
+ 'username' => 'This username is not available.',
529
+ ],
530
+ ];
531
+ }
532
+
533
+ return [
534
+ 'success' => true,
535
+ 'errors' => [],
536
+ 'normalized' => [
537
+ 'username' => $username,
538
+ ],
539
+ ];
540
+ }
541
+ ?>
542
+
543
+ <input
544
+ type="text"
545
+ value="{username}"
546
+ oninput="validateUsername(event.target.value)"
547
+ />
548
+ <p hidden="{!usernameError}">{usernameError}</p>
549
+
550
+ <script>
551
+ const [username, setUsername] = pp.state('');
552
+ const [usernameError, setUsernameError] = pp.state('');
553
+
554
+ async function validateUsername(val) {
555
+ setUsername(val);
556
+
557
+ const response = await pp.fetchFunction(
558
+ 'validateUsername',
559
+ { username: val },
560
+ { abortPrevious: true }
561
+ );
562
+
563
+ setUsernameError(response.errors?.username ?? '');
564
+ }
565
+ </script>
566
+ ```
567
+
568
+ ### Dedicated JSON endpoint with `route.php`
569
+
570
+ ```php filename="src/app/reports/route.php"
571
+ <?php
572
+
573
+ echo json_encode([
574
+ 'success' => true,
575
+ 'generatedAt' => date('c'),
576
+ ]);
577
+ ```
578
+
579
+ ## Loading states
580
+
581
+ Unlike Next.js, Prisma PHP’s data-fetching story here is not centered on React Suspense or streaming server components.
582
+
583
+ Instead, loading behavior is typically handled in the UI with:
584
+
585
+ - local PulsePoint state
586
+ - `pp-loading-content`
587
+ - route-level `loading.php`
588
+ - custom interactive state patterns
589
+
590
+ For interactive fetches, you usually manage loading state yourself:
591
+
592
+ ```html
593
+ <script>
594
+ const [loading, setLoading] = pp.state(false);
595
+ const [items, setItems] = pp.state([]);
596
+
597
+ async function loadItems() {
598
+ setLoading(true);
599
+
600
+ try {
601
+ const response = await pp.fetchFunction("getItems");
602
+ setItems(response.items ?? []);
603
+ } finally {
604
+ setLoading(false);
605
+ }
606
+ }
607
+ </script>
608
+ ```
609
+
610
+ ## Security and backend rules
611
+
612
+ When fetching data from Prisma PHP:
613
+
614
+ - validate incoming values on the server
615
+ - use `Validator` as the default PHP validation layer for interactive requests
616
+ - prefer the `Rule` builder for rule-based validation
617
+ - every function called through `pp.fetchFunction(...)` must use `#[Exposed]`
618
+ - return structured validation results instead of relying on thrown exceptions for routine failures
619
+ - use `Exposed` options to enforce auth, roles, or rate limits
620
+ - prefer `route.php` for dedicated public or integration-facing endpoints
621
+ - keep sensitive database access on the server side
622
+
623
+ Because Prisma PHP runs the data logic in PHP, database credentials and internal query logic stay on the server.
624
+
625
+ ## Good to know
626
+
627
+ - Prisma PHP’s primary interactive fetch model is `pp.fetchFunction(...)`.
628
+ - `index.php` is the UI entry point for page rendering.
629
+ - `route.php` is the direct handler entry point for API-style routes.
630
+ - `pp.fetchFunction(...)` only works for functions that are explicitly marked with `#[Exposed]`.
631
+ - `pp.fetchFunction(...)` automatically serializes and parses data.
632
+ - `Validator` is the default PHP validation layer for reactive requests and backend handlers.
633
+ - prefer `Validator::withRules(...)` with `Rule` for rule-based validation.
634
+ - `abortPrevious: true` helps cancel stale requests.
635
+ - file uploads can be handled automatically without manual `FormData` in many cases.
636
+ - `Exposed` can be used to declare authentication, allowed roles, and rate limits for callable functions.
637
+ - for full-stack routes, load page data in `index.php` and use direct function invocation for interactive updates.