-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathindex.html
More file actions
846 lines (833 loc) · 33.6 KB
/
Copy pathindex.html
File metadata and controls
846 lines (833 loc) · 33.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>SproutPHP Documentation</title>
<meta name="description" content="Official documentation for SproutPHP, a modern, minimalist PHP framework. Learn how to build fast, beautiful web apps with SproutPHP." />
<meta name="keywords" content="PHP, framework, SproutPHP, documentation, docs, web development, open source" />
<meta name="author" content="Yanik Kumar, SproutPHP" />
<meta property="og:title" content="SproutPHP Documentation" />
<meta property="og:description" content="Official documentation for SproutPHP, a modern, minimalist PHP framework. Learn how to build fast, beautiful web apps with SproutPHP." />
<meta property="og:type" content="website" />
<meta property="og:url" content="https://sproutphp.github.io/documentation/" />
<meta property="og:image" content="https://sproutphp.github.io/img/SproutPHP_Logo.png" />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content="SproutPHP Documentation" />
<meta name="twitter:description" content="Official documentation for SproutPHP, a modern, minimalist PHP framework. Learn how to build fast, beautiful web apps with SproutPHP." />
<meta name="twitter:image" content="https://sproutphp.github.io/img/SproutPHP_Logo.png" />
<meta name="twitter:site" content="@SproutPHP" />
<link rel="canonical" href="https://sproutphp.github.io/documentation/" />
<link rel="stylesheet" href="style.css" />
<link rel="icon" type="image/x-icon" href="favicon.ico" />
<style>
.beta-ribbon {
position: absolute;
top: 0;
right: 0;
background: #ff9800;
color: white;
padding: 5px 10px;
font-weight: bold;
font-size: 14px;
text-decoration: none;
border-bottom-left-radius: 5px;
z-index: 999;
}@media (max-width: 700px) {
.edit-btn {
display: none;
}
}
</style>
</head>
<body>
<!-- Remove beta ribbon if present -->
<!-- Add Edit on GitHub ribbon -->
<div style="position: fixed; top: 0; right: 0; z-index: 10000;" class="edit-btn">
<a href="https://github.com/SproutPHP/documentation/tree/main" target="_blank" style="display: block; background: #23272f; color: #fff; padding: 0.6em 1.2em; border-bottom-left-radius: 8px; font-weight: bold; text-decoration: none; box-shadow: 0 2px 8px #0002; font-size: 1.1em; border: 2px solid #4ade80; border-top: none; border-right: none;">
✏️ Edit on GitHub
</a>
</div>
<div class="mobile-header">
<button
id="sidebar-toggle"
aria-label="Toggle sidebar"
class="mobile-hamburger"
>
☰
</button>
<img
src="img/SproutPHP_Logo.png"
alt="SproutPHP Logo"
class="mobile-logo-img"
style="
height: 2rem;
width: auto;
vertical-align: middle;
margin-right: 0.5rem;
"
/>
<span class="mobile-logo">SproutPHP</span>
<button
id="darkmode-toggle"
title="Toggle dark mode"
class="mobile-darkmode-toggle"
>
🌙
</button>
</div>
<div class="docs-container">
<aside class="sidebar">
<div class="sidebar-header">
<div
class="sidebar-title-row"
style="
display: flex;
align-items: center;
justify-content: space-between;
gap: 1rem;
margin-bottom: 20px;
"
>
<img
src="img/SproutPHP_Logo.png"
alt="SproutPHP Logo"
class="sidebar-logo"
style="height: 2.1rem; width: auto; vertical-align: middle"
/>
<h2 style="margin: 0">SproutPHP</h2>
<button
id="darkmode-toggle"
title="Toggle dark mode"
style="
font-size: 1.3rem;
line-height: 1.3rem;
background: none;
border: none;
cursor: pointer;
outline: none;
padding: 1px;
"
>
🌙
</button>
</div>
<input type="text" id="search" placeholder="Search docs..." />
</div>
<nav>
<ul>
<li><a href="#introduction" class="nav-link">Introduction</a></li>
<li>
<a href="#project-structure" class="nav-link"
>Project Structure</a
>
</li>
<li><a href="#server-config-examples" class="nav-link">Server Config Examples</a></li>
<li>
<a href="#getting-started" class="nav-link">Getting Started</a>
</li>
<li><a href="#routing" class="nav-link">Routing</a></li>
<li><a href="#controllers" class="nav-link">Controllers</a></li>
<li><a href="#views" class="nav-link">Views & Templating</a></li>
<li><a href="#csrf" class="nav-link">CSRF Protection</a></li>
<li><a href="#csp" class="nav-link">CSP Strictness</a></li>
<li><a href="#htmx" class="nav-link">HTMX & SPA</a></li>
<li><a href="#storage" class="nav-link">Storage</a></li>
<li><a href="#file-upload" class="nav-link">File Upload</a></li>
<li>
<a href="#private-files" class="nav-link"
>Private File Handling</a
>
</li>
<li><a href="#debugbar" class="nav-link">Debugbar</a></li>
<li><a href="#testing" class="nav-link">Testing</a></li>
<li><a href="#changelog" class="nav-link">Changelog</a></li>
<li><a href="#release-notes" class="nav-link">Release Notes</a></li>
</ul>
</nav>
</aside>
<div class="sidebar-backdrop hide"></div>
<main class="content" id="main-content">
<section id="introduction">
<h1>Welcome to SproutPHP</h1>
<p>
<strong>SproutPHP</strong> is a modern, minimalist,
batteries-included PHP framework for rapid web development. It is
designed to be fast, clean, and easy to use, with a focus on
developer happiness and productivity.
</p>
<ul>
<li>Version: <b>v0.1.7</b></li>
<li>Release Date: 2025-07-18</li>
<li>License: MIT</li>
<li>
Author: <a href="https://github.com/yanikkumar">Yanik Kumar</a>
</li>
</ul>
<p>
SproutPHP is for developers who want to build modern web apps with
PHP, HTML, and CSS—without the bloat of heavy JS frameworks or large
dependency trees. It is minimal, but <b>batteries-included</b>:
everything you need for a beautiful, interactive web app is ready
out of the box.
</p>
<ul>
<li>
No heavy JavaScript: Use
<a href="https://htmx.org/" target="_blank">HTMX</a> for
interactivity, not SPA frameworks.
</li>
<li>
Minimal dependencies: Only essential Composer packages, no
<code>node_modules</code> or asset pipeline.
</li>
<li>
MVC structure: Clear separation of Controllers, Models, and Views.
</li>
<li>
Twig templating: Clean, secure, and fast rendering (optional to
swap for native PHP if desired).
</li>
</ul>
</section>
<!-- SECURITY WARNING: Only expose public/ as web root -->
<section id="security-warning">
<div style="background:#fff3f3; color:#b30000; border:1px solid #ffb3b3; padding:1.2em 1.5em; border-radius:7px; margin-bottom:2em; font-size:1.08em;">
<strong>⚠️ Security Warning:</strong><br>
For security, you <b>must</b> set your web server's document root to the <code>public/</code> directory only.<br>
<b>Never expose the project root or any directory above <code>public/</code> to the web.</b><br>
If misconfigured, sensitive files (like <code>.env</code>, <code>storage/</code>, <code>config/</code>, etc.) could be publicly accessible and compromise your application.<br>
<b>See <a href="#server-config-examples">Server Configuration</a> for deployment details.</b>
</div>
</section>
<section id="project-structure">
<h2>Project Structure</h2>
<p>
SproutPHP uses a clean, minimal directory structure for clarity and
maintainability:
</p>
<pre><code>project-root/
├── app/
│ ├── Controllers/
│ ├── Models/
│ ├── Views/
├── config/
├── core/
├── public/
├── routes/
├── storage/
├── vendor/
</code></pre>
<ul>
<li><b>app/Controllers/</b> — Your application controllers</li>
<li>
<b>app/Models/</b> — Your models (database access, business logic)
</li>
<li><b>app/Views/</b> — Twig templates and view partials</li>
<li>
<b>config/</b> — Configuration files (app, database, security,
etc.)
</li>
<li>
<b>core/</b> — Framework core (routing, support, middleware, etc.)
</li>
<li>
<b>public/</b> — Web root (index.php, assets, favicon, storage
symlink)
</li>
<li><b>routes/</b> — Route definitions (web.php)</li>
<li><b>storage/</b> — File uploads, cache, logs, etc.</li>
<li><b>vendor/</b> — Composer dependencies</li>
</ul>
</section>
<!-- Example Server Configuration (Apache & Nginx) -->
<section id="server-config-examples">
<h2>Server Configuration (Apache & Nginx)</h2>
<h3>Apache (.htaccess)</h3>
<pre><code class="language-apache"># Place this in your project root (not public/)
# Redirect all requests to public/
RewriteEngine On
RewriteCond %{REQUEST_URI} !^/public/
RewriteRule ^(.*)$ /public/$1 [L]
# Deny access to sensitive files everywhere
<FilesMatch "^(\.env|\.git|composer\.(json|lock)|config\.php)$">
Order allow,deny
Deny from all
</FilesMatch>
</code></pre>
<h3>Nginx</h3>
<pre><code class="language-nginx">server {
listen 80;
server_name yourdomain.com;
# Set the root to the public directory
root /path/to/your/project/public;
index index.php index.html;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
# Deny access to sensitive files
location ~ /\.(env|git|htaccess) {
deny all;
}
location ~* /(composer\.json|composer\.lock|config\.php) {
deny all;
}
location ~ \.php$ {
fastcgi_pass unix:/var/run/php/php8.2-fpm.sock;
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
}
</code></pre>
</section>
<section id="getting-started">
<h2>Getting Started</h2>
<ol>
<li>
<b>Clone the repository:</b>
<pre>git clone https://github.com/SproutPHP/framework.git</pre>
</li>
<li>
<b>Install dependencies:</b>
<pre>composer install</pre>
</li>
<li>
<b>Start the dev server:</b>
<pre>php sprout grow</pre>
</li>
<li>
<b>Open:</b>
<pre>http://localhost:9090</pre>
</li>
<li>
Edit <code>routes/web.php</code>, <code>app/Controllers</code>,
and <code>app/Views</code> to build your app.
</li>
</ol>
<p>Use the CLI to scaffold files:</p>
<pre><code>php sprout make:controller HomeController
php sprout make:model Post
php sprout make:view home
php sprout make:route posts
# ...and more (run `php sprout` for all commands)</code></pre>
</section>
<section id="routing">
<h2>Routing</h2>
<p>SproutPHP supports flexible route parameters (like most modern frameworks):</p>
<ul>
<li><code>{param}</code> — required parameter (matches anything except <code>/</code>)</li>
<li><code>{param?}</code> — optional parameter (trailing slash and parameter are optional)</li>
<li><code>{param:regex}</code> — required with custom regex</li>
<li><code>{param?:regex}</code> — optional with custom regex</li>
</ul>
<table>
<thead><tr><th>Pattern</th><th>Matches</th><th>Example URI</th><th>Notes</th></tr></thead>
<tbody>
<tr><td><code>/user/{id}</code></td><td>Yes</td><td>/user/123</td><td><code>{id}</code> = 123</td></tr>
<tr><td><code>/user/{id?}</code></td><td>Yes</td><td>/user/123, /user</td><td><code>{id}</code> = 123 or null</td></tr>
<tr><td><code>/file/{path:.+}</code></td><td>Yes</td><td>/file/foo/bar</td><td><code>{path}</code> = foo/bar</td></tr>
<tr><td><code>/file/{path?:.+}</code></td><td>Yes</td><td>/file, /file/foo/bar</td><td><code>{path}</code> = foo/bar or null</td></tr>
<tr><td><code>/blog/{slug}</code></td><td>Yes</td><td>/blog/hello-world</td><td><code>{slug}</code> = hello-world</td></tr>
<tr><td><code>/blog/{slug}</code></td><td>No</td><td>/blog/hello/world</td><td><code>{slug}</code> does not match <code>/</code></td></tr>
</tbody>
</table>
<p>Optional parameters are passed as <code>null</code> if missing. For catch-all (wildcard) parameters, use a custom regex like <code>{path:.+}</code>.</p>
<p>
Define routes in <code>routes/web.php</code> using a simple,
expressive API. SproutPHP supports dynamic parameters for flexible
CRUD and APIs:
</p>
<pre><code>Route::get('/user/{id}', 'UserController@show');
Route::get('/blog/{slug}', 'BlogController@show');</code></pre>
<ul>
<li>Parameters are passed to controller methods as arguments.</li>
<li>All routes are defined in <code>routes/web.php</code>.</li>
</ul>
<p>For more, see the <b>Controllers</b> section.</p>
</section>
<section id="controllers">
<h2>Controllers</h2>
<p>
Controllers handle requests and return responses. Create controllers
in <code>app/Controllers/</code>:
</p>
<pre><code>// app/Controllers/HomeController.php
class HomeController {
public function index() {
return view('home', ['title' => 'Hello, World!']);
}
}</code></pre>
<p>
Controllers can return views, JSON, or any response. Use route
parameters as method arguments.
</p>
</section>
<section id="views">
<h2>Views & Templating</h2>
<p>
SproutPHP uses
<a href="https://twig.symfony.com/" target="_blank">Twig</a> for
templating by default. Views are in <code>app/Views/</code>:
</p>
<pre><code>{# app/Views/home.twig #}
<h1>{{ title }}</h1></code></pre>
<ul>
<li>Pass data from controllers to views as associative arrays.</li>
<li>Use layouts, partials, and components for DRY templates.</li>
<li>
All helpers in <code>core/Support/helpers.php</code> are available
in Twig.
</li>
</ul>
</section>
<section id="csrf">
<h2>CSRF Protection</h2>
<p>
SproutPHP uses robust, middleware-based CSRF protection for all
state-changing requests (POST, PUT, PATCH, DELETE):
</p>
<ul>
<li>
Token is generated and stored in <code>_csrf_token</code> in the
session.
</li>
<li>
Use <code>{{ csrf_field()|raw }}</code> in forms for a hidden
input.
</li>
<li>
Use <code>{{ csrf_token() }}</code> for AJAX/HTMX requests (in the
<code>X-CSRF-TOKEN</code> header).
</li>
<li>
Middleware <code>VerifyCsrfToken</code> validates the token for
all state-changing requests.
</li>
</ul>
<pre><code>// In a form
<form method="POST">
{{ csrf_field()|raw }}
...
</form>
// In HTMX
<button hx-post="/api" hx-headers='{"X-CSRF-TOKEN": "{{ csrf_token() }}"}'>...</button></code></pre>
</section>
<section id="csp">
<h2>Content Security Policy (CSP) and External APIs/Images</h2>
<p>
<b>By default, SproutPHP sets a strict Content Security Policy (CSP) to maximize security:</b>
<ul>
<li>Only resources from your own domain are allowed (<code>default-src 'self'</code>).</li>
<li>No external APIs (AJAX/fetch) or images are permitted by default.</li>
</ul>
</p>
<h3>Allowing External APIs (connect-src)</h3>
<p>
To allow your app to fetch data from external APIs (e.g., GitHub, third-party services), set the <code>CSP_CONNECT_SRC</code> variable in your <code>.env</code> file:
</p>
<pre><code>CSP_CONNECT_SRC=https://api.github.com,https://another.api.com</code></pre>
<p>
This will add the specified domains to the CSP <code>connect-src</code> directive, allowing JavaScript to make requests to those APIs.
</p>
<h3>Allowing External Images (img-src)</h3>
<p>
To allow your app to load images from external sources (e.g., shields.io, Gravatar), set the <code>CSP_IMG_SRC</code> variable in your <code>.env</code> file:
</p>
<pre><code>CSP_IMG_SRC=https://img.shields.io,https://www.gravatar.com</code></pre>
<p>
This will add the specified domains to the CSP <code>img-src</code> directive, allowing images from those sources.
</p>
<h3>Why is CSP strict by default?</h3>
<ul>
<li>This prevents accidental data leaks and XSS attacks by only allowing resources from your own domain.</li>
<li>You must explicitly allow any external domains you trust for APIs or images.</li>
</ul>
<h3>Where is this configured?</h3>
<ul>
<li>See <code>config/security.php</code> for how these variables are loaded.</li>
<li>The CSP header is set in the <code>XssProtection</code> middleware.</li>
</ul>
<b>Tip:</b> Only add domains you trust and actually use. Never use <code>*</code> in production for these settings.
</section>
<section id="htmx">
<h2>HTMX & SPA Interactivity</h2>
<p>
SproutPHP uses
<a href="https://htmx.org/" target="_blank">HTMX</a> for modern,
SPA-like interactivity:
</p>
<ul>
<li>Partial page updates (no full reloads)</li>
<li>Request indicators (spinners)</li>
<li>
File uploads and forms with <code>hx-post</code>,
<code>hx-target</code>, <code>hx-swap</code>,
<code>hx-indicator</code>
</li>
<li>
Use <code>hx-trigger="submit delay:500ms"</code> to delay requests
for UX/demo
</li>
</ul>
<pre><code><form
hx-post="/validation-test"
hx-target="#form-container"
hx-swap="innerHTML"
hx-indicator="#spinner"
hx-trigger="submit delay:500ms"
>
...
</form></code></pre>
<p>
See the <b>File Upload</b> section for an example with file input.
</p>
</section>
<section id="storage">
<h2>Storage</h2>
<p>
SproutPHP provides robust file storage with public/private
separation:
</p>
<ul>
<li>
Public files: <code>storage/app/public</code> (served via symlink
to <code>public/storage</code>)
</li>
<li>
Private files: <code>storage/app/private</code> (not
web-accessible)
</li>
<li>Configurable in <code>config/storage.php</code></li>
<li>
Use <code>Storage::put()</code>, <code>Storage::url()</code>,
<code>Storage::path()</code>
</li>
</ul>
<pre><code>// Upload public
$path = Storage::put($file, 'avatars');
$url = Storage::url($path);
// Upload private
$path = Storage::put($file, '', 'private');
$fullPath = Storage::path($path, 'private');</code></pre>
</section>
<section id="file-upload">
<h2>File Upload</h2>
<p>File uploads are easy with SproutPHP and HTMX:</p>
<pre><code><form
hx-post="/validation-test"
hx-target="#form-container"
hx-swap="innerHTML"
hx-indicator="#spinner"
enctype="multipart/form-data"
>
{{ csrf_field()|raw }}
<input type="file" name="avatar" />
<button type="submit">Upload</button>
</form></code></pre>
<ul>
<li>
Files are stored in <code>storage/app/public</code> or
<code>storage/app/private</code>
</li>
<li>
Use <code>Storage::put()</code> to save,
<code>Storage::url()</code> to get public URL
</li>
<li>Private files are only accessible via controller download</li>
</ul>
</section>
<section id="private-files">
<h2>Private File Handling</h2>
<p>
Private files are stored in <code>storage/app/private</code> and are
not web-accessible. They can only be accessed via internal
controller methods, ensuring security for sensitive uploads.
</p>
<pre><code>// Upload private
$path = Storage::put($file, '', 'private');
// Download (controller)
$privatePath = Storage::path($filename, 'private');
</code></pre>
<ul>
<li>No direct links to private files are exposed.</li>
<li>
To serve a private file, stream it from a controller after
permission checks.
</li>
</ul>
</section>
<section id="debugbar">
<h2>Debugbar</h2>
<p>
The SproutPHP Debugbar shows request info, queries, and performance
at the bottom of the page (when <code>APP_DEBUG</code> is enabled):
</p>
<ul>
<li>
Shows method, URI, query count, query time, page time, memory
usage
</li>
<li>Lists all SQL queries with parameters and call site</li>
<li>Auto-updates URI after HTMX navigation</li>
<li>
Can be customized or hidden in
<code>core/Support/Debugbar.php</code>
</li>
</ul>
</section>
<section id="testing">
<h2>Testing Your SproutPHP App</h2>
<p>SproutPHP is compatible with <a href="https://phpunit.de/" target="_blank">PHPUnit</a> and other popular PHP testing tools.</p>
<ol>
<li><b>Install PHPUnit (dev only):</b><br>
<code>composer require --dev phpunit/phpunit</code>
</li>
<li><b>Create a <code>tests/</code> directory</b> in your project root.</li>
<li><b>Add a sample test:</b>
<pre><code>// tests/ExampleTest.php
use PHPUnit\Framework\TestCase;
class ExampleTest extends TestCase
{
public function testBasicAssertion()
{
$this->assertTrue(true);
}
}
</code></pre>
</li>
<li><b>Run your tests:</b><br>
<code>./vendor/bin/phpunit</code>
</li>
</ol>
<p>You can test any part of your app: helpers, models, controllers, middleware, etc. Use mocks and stubs as needed.</p>
<p><b>Note:</b> SproutPHP does not include test files by default. You are free to organize and write tests as you see fit for your project.</p>
</section>
<section id="changelog">
<h2>Changelog</h2>
<pre><code>## [v0.1.7] - 2025-07-18
### Added
- Dynamic route parameter support (e.g., /user/{id}, /file/{filename:.+}) for CRUD and flexible routing
- Robust CSRF protection via middleware and helpers (works for forms, AJAX, and HTMX)
- SPA-like file upload and form handling with HTMX (including indicators and grid UI)
- Secure private file upload/download (no direct links, internal access only)
- Consistent CSRF token management (single session key, helpers, and middleware)
### Improved
- UI/UX for validation and file upload forms (two-column grid, spinner, SPA feel)
- Path resolution for storage (public/private separation, symlink support)
- Code structure: CSRF logic moved to helpers/middleware, no raw PHP in entry
### Fixed
- Issues with file download on PHP built-in server (now uses query param for compatibility)
- Consistency in CSRF token usage across the framework
### Removed
- Exposed raw CSRF logic from entry point
</code></pre>
</section>
<section id="release-notes">
<h2>Release Notes: v0.1.7 (2025-07-18)</h2>
<ul>
<li>
<b>First Beta Release!</b> SproutPHP is now feature-complete and
ready for broader testing and feedback.
</li>
<li>
<b>Dynamic Routing:</b> Support for route parameters (e.g.,
/user/{id}, /file/{filename:.+}) enables full CRUD and flexible
APIs.
</li>
<li>
<b>CSRF Protection:</b> Robust, middleware-based CSRF protection
for all state-changing requests (forms, AJAX, HTMX).
</li>
<li>
<b>SPA-like UX:</b> HTMX-powered forms and file uploads for a
modern, seamless user experience.
</li>
<li>
<b>Private File Handling:</b> Secure upload/download of private
files, accessible only via internal methods.
</li>
<li>
<b>Cleaner Codebase:</b> All CSRF logic is now in
helpers/middleware, not exposed in entry scripts.
</li>
</ul>
<h3>Upgrade Notes</h3>
<ul>
<li>
All CSRF tokens now use the <code>_csrf_token</code> session key.
Update any custom code to use the new helpers.
</li>
<li>
File downloads now use a query parameter (<code>?file=...</code>)
for compatibility with the PHP built-in server.
</li>
<li>
If you use custom routes, you can now use <code>{param}</code> and
<code>{param:regex}</code> patterns.
</li>
</ul>
<h3>What's New</h3>
<ul>
<li>Two-column grid UI for validation and file upload forms</li>
<li>SPA feel with HTMX indicators and partial updates</li>
<li>Consistent and secure CSRF handling everywhere</li>
<li>Improved storage path resolution and symlink support</li>
</ul>
<p>
Thank you for testing and contributing to SproutPHP. Please report
any issues or feedback as we move toward a stable release.
</p>
</section>
<!-- End of documentation sections -->
<section id="about-branding" class="about-branding">
<img
src="img/SproutPHP_Icon.png"
alt="SproutPHP Icon"
class="about-branding-logo"
/>
<h2>About SproutPHP Branding & Creator</h2>
<p class="about-branding-desc">
SproutPHP is a modern, minimalist PHP framework designed and created
by
<a href="https://github.com/yanikkumar" target="_blank"
>Yanik Kumar</a
>. The SproutPHP logo and icon represent growth, simplicity, and the
power of starting small to build something great.
</p>
<blockquote class="about-branding-quote">
“From tiny seeds grow mighty trees. — SproutPHP”
</blockquote>
<div class="about-branding-sponsor">
<div style="margin-top: 1em; margin-bottom: 1em">
<a
href="https://github.com/sponsors/yanikkumar"
target="_blank"
rel="noopener"
style="text-decoration: none; color: inherit"
>
<div
style="
display: flex;
align-items: center;
background: #161b22;
border-radius: 8px;
padding: 1em;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
max-width: 420px;
margin: 0 auto;
"
>
<img
src="https://avatars.githubusercontent.com/u/44070102?v=4"
alt="Yanik Kumar avatar"
style="
width: 48px;
height: 48px;
border-radius: 50%;
margin-right: 1em;
border: 2px solid #30363d;
background: #222;
"
/>
<div style="flex: 1">
<div
style="font-size: 1.1em; font-weight: bold; color: #fff"
>
Sponsor Yanik Kumar on GitHub Sponsors
</div>
<div
style="
font-size: 0.97em;
color: #adbac7;
margin-top: 0.2em;
"
>
Support yanikkumar's open source work
</div>
</div>
<button
style="
margin-left: 1em;
background: #db2777;
color: #fff;
border: none;
border-radius: 6px;
padding: 0.5em 1.2em;
font-size: 1em;
font-weight: 600;
cursor: pointer;
display: flex;
align-items: center;
gap: 0.4em;
box-shadow: 0 1px 4px rgba(219, 39, 119, 0.12);
transition: background 0.2s;
"
>
<span style="font-size: 1.2em">❤️</span>
Sponsor
</button>
</div>
</a>
</div>
</div>
<footer
class="footer"
style="
margin-top: 2rem;
padding: 1.5rem 0;
border-top: 1px solid #eee;
text-align: center;
font-size: 0.95rem;
color: #888;
"
>
<div class="footer-follow">
<span>Follow us:</span>
<a
href="https://github.com/SproutPHP"
target="_blank"
rel="noopener"
aria-label="GitHub"
>
<svg
width="24"
height="24"
viewbox="0 0 24 24"
fill="currentColor"
style="vertical-align: middle"
>
<path
d="M12 2C6.477 2 2 6.484 2 12.021c0 4.428 2.865 8.184 6.839 9.504.5.092.682-.217.682-.482 0-.237-.009-.868-.014-1.703-2.782.605-3.369-1.342-3.369-1.342-.454-1.154-1.11-1.462-1.11-1.462-.908-.62.069-.608.069-.608 1.004.07 1.532 1.032 1.532 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.339-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.987 1.029-2.686-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.025A9.564 9.564 0 0 1 12 6.844c.85.004 1.705.115 2.504.337 1.909-1.295 2.748-1.025 2.748-1.025.546 1.378.202 2.397.1 2.65.64.699 1.028 1.593 1.028 2.686 0 3.847-2.338 4.695-4.566 4.944.359.309.678.919.678 1.852 0 1.336-.012 2.415-.012 2.744 0 .267.18.579.688.481C19.138 20.203 22 16.447 22 12.021 22 6.484 17.523 2 12 2z"
/>
</svg>
</a>
<a
href="https://twitter.com/SproutPHP"
target="_blank"
rel="noopener"
aria-label="Twitter"
>
<svg
width="24"
height="24"
viewbox="0 0 24 24"
fill="currentColor"
style="vertical-align: middle"
>
<path
d="M17.53 2H21.5L14.36 10.09L22.75 22H15.97L10.98 15.18L5.29 22H1.32L8.87 13.36L1 2H7.97L12.54 8.22L17.53 2ZM16.32 20H18.34L7.75 3.97H5.59L16.32 20Z"
/>
</svg>
</a>
</div>
<div style="margin-top: 0.7em; font-size: 0.98em; color: var(--muted)">
© <span id="copyright-year"></span> SproutPHP. All rights reserved.
</div>
</footer>
</section>
</main>
</div>
<script src="docs.js"></script>
</body>
</html>