spark-php/framework

Spark — a lightweight, Laravel-inspired PHP framework with routing, ORM, templating, and CLI

Maintainers

Package info

github.com/pawan1793/spark

pkg:composer/spark-php/framework

Statistics

Installs: 10

Dependents: 1

Suggesters: 0

Stars: 0

Open Issues: 0

v1.0.8 2026-04-17 04:26 UTC

This package is auto-updated.

Last update: 2026-04-17 04:27:31 UTC


README

Tests Latest Version PHP Version License

A lightweight, Laravel-inspired PHP framework. No Symfony dependencies, no heavy abstractions — just routing, ORM, templating, middleware, DI, migrations, and a CLI in clean PHP 8.1+ code.

Quick Start

composer create-project spark-php/skeleton my-app
cd my-app
php spark serve

Open http://localhost:8000.

No extra configuration needed — the app key is generated automatically and SQLite is configured out of the box.

Requirements

  • PHP 8.1+
  • Extensions: pdo, mbstring, json
  • No other dependencies

Routing

routes/web.php:

$router->get('/', [HomeController::class, 'index']);
$router->get('/users/{id}', [UserController::class, 'show']);
$router->get('/posts/{slug?}', [PostController::class, 'show']); // optional param

$router->post('/users', [UserController::class, 'store'])
    ->middleware(App\Middleware\Auth::class);

$router->group(['prefix' => 'admin', 'middleware' => [Auth::class]], function ($router) {
    $router->get('/dashboard', [DashboardController::class, 'index']);
});

$router->get('/ping', fn() => ['pong' => true]);

// API routes (routes/api.php) — no CSRF, prefixed /api
$router->get('/users', [UserController::class, 'index'])->name('api.users');

// Webhook — opt out of CSRF individually
$router->post('/webhook/github', [WebhookController::class, 'handle'])->withoutCsrf();

Route params are injected into controller methods by name.

Controllers

namespace App\Controllers;

use Spark\Http\Request;
use Spark\Http\Response;

class UserController
{
    public function show(Request $request, string $id): Response
    {
        $user = User::find($id);
        return json($user);
    }

    public function store(Request $request): Response
    {
        $user = User::create($request->only(['name', 'email']));
        return json($user, 201);
    }
}

Return a Response, an array/object (auto-JSON), or a string (HTML).

Models (ORM)

namespace App\Models;

use Spark\Database\Model;

class User extends Model
{
    protected static array $fillable = ['name', 'email'];
    protected static bool  $timestamps = true;
}

// CRUD
$user   = User::create(['name' => 'Ada', 'email' => 'ada@x.com']);
$all    = User::all();
$one    = User::find(1);
$adults = User::where('age', '>', 18)->orderBy('name')->limit(10)->get();
$user->update(['name' => 'Ada Lovelace']);
$user->delete();

Relationships: hasOne, hasMany, belongsTo.

Migrations

use Spark\Database\{Migration, Schema, Blueprint};

return new class extends Migration {
    public function up(): void {
        Schema::create('posts', function (Blueprint $t) {
            $t->id();
            $t->string('title');
            $t->text('body');
            $t->foreignId('user_id');
            $t->timestamps();
        });
    }
    public function down(): void {
        Schema::dropIfExists('posts');
    }
};

Supports SQLite, MySQL, and PostgreSQL.

php spark migrate
php spark migrate:rollback

Blade-lite Views (.spark.php)

@extends('layout')
@section('title', 'Home')
@section('content')
  <h1>{{ $title }}</h1>
  @foreach($items as $item)
    <li>{{ $item }}</li>
  @endforeach
  @if($user)
    Welcome, {{ $user->name }}
  @endif
@endsection

Templates compile once to plain PHP in storage/cache/views/. Subsequent requests use the cached version.

Directives: @extends @section @endsection @yield @include @if @elseif @else @endif @foreach @endforeach @for @while @isset @empty @unless @php @endphp
Echo: {{ $var }} (HTML-escaped), {!! $html !!} (raw)

Middleware

namespace App\Middleware;

use Closure;
use Spark\Http\Request;
use Spark\Http\Response;

class Auth
{
    public function handle(Request $request, Closure $next): Response
    {
        if (!$request->header('authorization')) {
            abort(401, 'Token required');
        }
        return $next($request);
    }
}

Built-in: StartSession, VerifyCsrfToken, Cors, ForceHttps.

Service Providers

namespace App\Providers;

use Spark\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    public function register(): void
    {
        $this->app->singleton(PaymentGateway::class, fn() => new StripeGateway(
            config('services.stripe.key')
        ));
    }

    public function boot(): void
    {
        // Runs after all providers are registered
    }
}

Register in bootstrap/app.php:

$app->register(\App\Providers\AppServiceProvider::class);

Dependency Injection

Constructor-inject any class; the container resolves dependencies automatically:

class ReportController
{
    public function __construct(private UserRepository $repo) {}

    public function index(): Response
    {
        return json($this->repo->all());
    }
}

CLI

php spark serve                   # dev server (localhost:8000)
php spark make:controller Foo     # scaffold controller
php spark make:model Foo
php spark make:middleware Foo
php spark make:migration create_foo_table
php spark migrate
php spark migrate:rollback
php spark route:list
php spark key:generate

Security defaults

Every response gets these headers automatically:

Header Value
X-Content-Type-Options nosniff
X-Frame-Options SAMEORIGIN
Referrer-Policy strict-origin-when-cross-origin
Content-Security-Policy strict + nonce for inline scripts/styles
Strict-Transport-Security set on HTTPS
Permissions-Policy geolocation=(), microphone=(), camera=()

CSRF tokens, secure session cookies, SQL prepared statements, mass-assignment protection, and open-redirect prevention are all on by default.

Logging

Spark includes a built-in file logger. Logs are written to storage/logs/spark.log.

// Log at info level
logger('User registered', ['user_id' => $user->id]);

// Get the Logger instance for any level
logger()->debug('Cache miss', ['key' => $cacheKey]);
logger()->warning('Slow query detected', ['ms' => 850]);
logger()->error('Payment failed', ['order' => $orderId]);

Log format:

[2026-04-17 10:30:00] INFO: User registered {"user_id":42}
[2026-04-17 10:30:01] ERROR: Payment failed {"order":99}

The minimum log level is controlled by LOG_LEVEL in your .env file (default: debug).
Valid levels: debug, info, warning, error.

You can also inject the logger via the container:

use Spark\Support\Logger;

class OrderController
{
    public function __construct(private Logger $logger) {}

    public function store(Request $request): Response
    {
        // ...
        $this->logger->info('Order created', ['id' => $order->id]);
        return json($order, 201);
    }
}

Helpers

app(), config(), env(), base_path(), storage_path(), view(), json(), response(), redirect(), abort(), csrf_token(), csrf_field(), bcrypt(), csp_nonce(), e(), dd(), logger()

License

MIT — Copyright (c) 2026 Pawan More