Welcome to SproutPHP
SproutPHP 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.
- Version: v0.1.7
- Release Date: 2025-07-18
- License: MIT
- Author: Yanik Kumar
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 batteries-included: everything you need for a beautiful, interactive web app is ready out of the box.
- No heavy JavaScript: Use HTMX for interactivity, not SPA frameworks.
-
Minimal dependencies: Only essential Composer packages, no
node_modules
or asset pipeline. - MVC structure: Clear separation of Controllers, Models, and Views.
- Twig templating: Clean, secure, and fast rendering (optional to swap for native PHP if desired).
For security, you must set your web server's document root to the
public/
directory only.Never expose the project root or any directory above
public/
to the web.If misconfigured, sensitive files (like
.env
, storage/
, config/
, etc.) could be publicly accessible and compromise your application.See Server Configuration for deployment details.
Project Structure
SproutPHP uses a clean, minimal directory structure for clarity and maintainability:
project-root/
βββ app/
β βββ Controllers/
β βββ Models/
β βββ Views/
βββ config/
βββ core/
βββ public/
βββ routes/
βββ storage/
βββ vendor/
- app/Controllers/ β Your application controllers
- app/Models/ β Your models (database access, business logic)
- app/Views/ β Twig templates and view partials
- config/ β Configuration files (app, database, security, etc.)
- core/ β Framework core (routing, support, middleware, etc.)
- public/ β Web root (index.php, assets, favicon, storage symlink)
- routes/ β Route definitions (web.php)
- storage/ β File uploads, cache, logs, etc.
- vendor/ β Composer dependencies
Server Configuration (Apache & Nginx)
Apache (.htaccess)
# 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>
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;
}
}
Getting Started
-
Clone the repository:
git clone https://github.com/SproutPHP/framework.git
-
Install dependencies:
composer install
-
Start the dev server:
php sprout grow
-
Open:
http://localhost:9090
-
Edit
routes/web.php
,app/Controllers
, andapp/Views
to build your app.
Use the CLI to scaffold files:
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)
Routing
SproutPHP supports flexible route parameters (like most modern frameworks):
{param}
β required parameter (matches anything except/
){param?}
β optional parameter (trailing slash and parameter are optional){param:regex}
β required with custom regex{param?:regex}
β optional with custom regex
Pattern | Matches | Example URI | Notes |
---|---|---|---|
/user/{id} | Yes | /user/123 | {id} = 123 |
/user/{id?} | Yes | /user/123, /user | {id} = 123 or null |
/file/{path:.+} | Yes | /file/foo/bar | {path} = foo/bar |
/file/{path?:.+} | Yes | /file, /file/foo/bar | {path} = foo/bar or null |
/blog/{slug} | Yes | /blog/hello-world | {slug} = hello-world |
/blog/{slug} | No | /blog/hello/world | {slug} does not match / |
Optional parameters are passed as null
if missing. For catch-all (wildcard) parameters, use a custom regex like {path:.+}
.
Define routes in routes/web.php
using a simple,
expressive API. SproutPHP supports dynamic parameters for flexible
CRUD and APIs:
Route::get('/user/{id}', 'UserController@show');
Route::get('/blog/{slug}', 'BlogController@show');
- Parameters are passed to controller methods as arguments.
- All routes are defined in
routes/web.php
.
For more, see the Controllers section.
Controllers
Controllers handle requests and return responses. Create controllers
in app/Controllers/
:
// app/Controllers/HomeController.php
class HomeController {
public function index() {
return view('home', ['title' => 'Hello, World!']);
}
}
Controllers can return views, JSON, or any response. Use route parameters as method arguments.
Views & Templating
SproutPHP uses
Twig for
templating by default. Views are in app/Views/
:
{# app/Views/home.twig #}
{{ title }}
- Pass data from controllers to views as associative arrays.
- Use layouts, partials, and components for DRY templates.
-
All helpers in
core/Support/helpers.php
are available in Twig.
CSRF Protection
SproutPHP uses robust, middleware-based CSRF protection for all state-changing requests (POST, PUT, PATCH, DELETE):
-
Token is generated and stored in
_csrf_token
in the session. -
Use
{{ csrf_field()|raw }}
in forms for a hidden input. -
Use
{{ csrf_token() }}
for AJAX/HTMX requests (in theX-CSRF-TOKEN
header). -
Middleware
VerifyCsrfToken
validates the token for all state-changing requests.
// In a form
// In HTMX
Content Security Policy (CSP) and External APIs/Images
By default, SproutPHP sets a strict Content Security Policy (CSP) to maximize security:
- Only resources from your own domain are allowed (
default-src 'self'
). - No external APIs (AJAX/fetch) or images are permitted by default.
Allowing External APIs (connect-src)
To allow your app to fetch data from external APIs (e.g., GitHub, third-party services), set the CSP_CONNECT_SRC
variable in your .env
file:
CSP_CONNECT_SRC=https://api.github.com,https://another.api.com
This will add the specified domains to the CSP connect-src
directive, allowing JavaScript to make requests to those APIs.
Allowing External Images (img-src)
To allow your app to load images from external sources (e.g., shields.io, Gravatar), set the CSP_IMG_SRC
variable in your .env
file:
CSP_IMG_SRC=https://img.shields.io,https://www.gravatar.com
This will add the specified domains to the CSP img-src
directive, allowing images from those sources.
Why is CSP strict by default?
- This prevents accidental data leaks and XSS attacks by only allowing resources from your own domain.
- You must explicitly allow any external domains you trust for APIs or images.
Where is this configured?
- See
config/security.php
for how these variables are loaded. - The CSP header is set in the
XssProtection
middleware.
*
in production for these settings.
HTMX & SPA Interactivity
SproutPHP uses HTMX for modern, SPA-like interactivity:
- Partial page updates (no full reloads)
- Request indicators (spinners)
-
File uploads and forms with
hx-post
,hx-target
,hx-swap
,hx-indicator
-
Use
hx-trigger="submit delay:500ms"
to delay requests for UX/demo
<form
hx-post="/validation-test"
hx-target="#form-container"
hx-swap="innerHTML"
hx-indicator="#spinner"
hx-trigger="submit delay:500ms"
>
...
</form>
See the File Upload section for an example with file input.
Storage
SproutPHP provides robust file storage with public/private separation:
-
Public files:
storage/app/public
(served via symlink topublic/storage
) -
Private files:
storage/app/private
(not web-accessible) - Configurable in
config/storage.php
-
Use
Storage::put()
,Storage::url()
,Storage::path()
// Upload public
$path = Storage::put($file, 'avatars');
$url = Storage::url($path);
// Upload private
$path = Storage::put($file, '', 'private');
$fullPath = Storage::path($path, 'private');
File Upload
File uploads are easy with SproutPHP and HTMX:
<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>
-
Files are stored in
storage/app/public
orstorage/app/private
-
Use
Storage::put()
to save,Storage::url()
to get public URL - Private files are only accessible via controller download
Private File Handling
Private files are stored in storage/app/private
and are
not web-accessible. They can only be accessed via internal
controller methods, ensuring security for sensitive uploads.
// Upload private
$path = Storage::put($file, '', 'private');
// Download (controller)
$privatePath = Storage::path($filename, 'private');
- No direct links to private files are exposed.
- To serve a private file, stream it from a controller after permission checks.
Debugbar
The SproutPHP Debugbar shows request info, queries, and performance
at the bottom of the page (when APP_DEBUG
is enabled):
- Shows method, URI, query count, query time, page time, memory usage
- Lists all SQL queries with parameters and call site
- Auto-updates URI after HTMX navigation
-
Can be customized or hidden in
core/Support/Debugbar.php
Testing Your SproutPHP App
SproutPHP is compatible with PHPUnit and other popular PHP testing tools.
- Install PHPUnit (dev only):
composer require --dev phpunit/phpunit
- Create a
tests/
directory in your project root. - Add a sample test:
// tests/ExampleTest.php use PHPUnit\Framework\TestCase; class ExampleTest extends TestCase { public function testBasicAssertion() { $this->assertTrue(true); } }
- Run your tests:
./vendor/bin/phpunit
You can test any part of your app: helpers, models, controllers, middleware, etc. Use mocks and stubs as needed.
Note: SproutPHP does not include test files by default. You are free to organize and write tests as you see fit for your project.
Changelog
## [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
Release Notes: v0.1.7 (2025-07-18)
- First Beta Release! SproutPHP is now feature-complete and ready for broader testing and feedback.
- Dynamic Routing: Support for route parameters (e.g., /user/{id}, /file/{filename:.+}) enables full CRUD and flexible APIs.
- CSRF Protection: Robust, middleware-based CSRF protection for all state-changing requests (forms, AJAX, HTMX).
- SPA-like UX: HTMX-powered forms and file uploads for a modern, seamless user experience.
- Private File Handling: Secure upload/download of private files, accessible only via internal methods.
- Cleaner Codebase: All CSRF logic is now in helpers/middleware, not exposed in entry scripts.
Upgrade Notes
-
All CSRF tokens now use the
_csrf_token
session key. Update any custom code to use the new helpers. -
File downloads now use a query parameter (
?file=...
) for compatibility with the PHP built-in server. -
If you use custom routes, you can now use
{param}
and{param:regex}
patterns.
What's New
- Two-column grid UI for validation and file upload forms
- SPA feel with HTMX indicators and partial updates
- Consistent and secure CSRF handling everywhere
- Improved storage path resolution and symlink support
Thank you for testing and contributing to SproutPHP. Please report any issues or feedback as we move toward a stable release.

About SproutPHP Branding & Creator
SproutPHP is a modern, minimalist PHP framework designed and created by Yanik Kumar. The SproutPHP logo and icon represent growth, simplicity, and the power of starting small to build something great.
βFrom tiny seeds grow mighty trees. β SproutPHPβ