✏️ Edit on GitHub
SproutPHP Logo

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).
⚠️ Security Warning:
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

  1. Clone the repository:
    git clone https://github.com/SproutPHP/framework.git
  2. Install dependencies:
    composer install
  3. Start the dev server:
    php sprout grow
  4. Open:
    http://localhost:9090
  5. Edit routes/web.php, app/Controllers, and app/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
PatternMatchesExample URINotes
/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 the X-CSRF-TOKEN header).
  • Middleware VerifyCsrfToken validates the token for all state-changing requests.
// In a form
{{ csrf_field()|raw }} ...
// 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.
Tip: Only add domains you trust and actually use. Never use * 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 to public/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 or storage/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.

  1. Install PHPUnit (dev only):
    composer require --dev phpunit/phpunit
  2. Create a tests/ directory in your project root.
  3. Add a sample test:
    // tests/ExampleTest.php
    use PHPUnit\Framework\TestCase;
    
    class ExampleTest extends TestCase
    {
        public function testBasicAssertion()
        {
            $this->assertTrue(true);
        }
    }
    
  4. 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”