PHPackages                             vancil/flint - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [API Development](/categories/api)
4. /
5. vancil/flint

ActiveProject[API Development](/categories/api)

vancil/flint
============

Flint — fast, expressive PHP for production APIs

v1.0.1(1mo ago)042MITPHPPHP &gt;=8.1CI passing

Since May 31Pushed 1mo agoCompare

[ Source](https://github.com/Vancil/flint)[ Packagist](https://packagist.org/packages/vancil/flint)[ RSS](/packages/vancil-flint/feed)WikiDiscussions master Synced 1w ago

READMEChangelog (2)Dependencies (2)Versions (12)Used By (2)

Flint
=====

[](#flint)

 [![Tests](https://camo.githubusercontent.com/df0f3bcb37793d552b973ade18c83f559899f0638c05e76cddde0ec06e70b671/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f616374696f6e732f776f726b666c6f772f7374617475732f56616e63696c2f666c696e742f63692e796d6c3f6c6162656c3d7465737473)](https://github.com/Vancil/flint/actions/workflows/ci.yml) [![Total Downloads](https://camo.githubusercontent.com/2686587aa2be5137affc68a5b6915952e7f7efc84370673b77e70fb409e120d1/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f76616e63696c2f666c696e74)](https://packagist.org/packages/vancil/flint) [![Latest Version on Packagist](https://camo.githubusercontent.com/31f0dab4586247f16bb95a789b7d58c7734b3ccfa330805c157731a3d9575843/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f76616e63696c2f666c696e74)](https://packagist.org/packages/vancil/flint) [![License](https://camo.githubusercontent.com/b8cadaa967891081f8f165695470689986c028821dd8a040132f6e661795dc0d/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c6963656e73652d4d49542d626c7565)](https://github.com/Vancil/flint/blob/master/LICENSE)

A lightweight, fast PHP framework built for the web. Laravel-style ergonomics, session-based auth, a Blade-like template engine, and a fraction of the overhead. Expressive routing, Active Record ORM, queue jobs, schema builder, and a CLI — all with zero magic.

**PHP &gt;= 8.1 required.**

---

Philosophy
----------

[](#philosophy)

Flint is built around three ideas:

**Slim.** Core ships with only what every application needs — routing, ORM, validation, queues, sessions, templates, and a CLI. Nothing more. Auth scaffolding, mail, and third-party integrations are available as installable packages so you only carry what you actually use.

**Fast.** The entire framework boots in a single `Application::boot()` call. No deferred service providers, no lazy container chains, no runtime class generation. What you see is what runs.

**Auditable.** No facades, no static proxies, no magic. Every dependency is injected directly and every call can be followed in a debugger. The full framework source is small enough to read in an afternoon.

### Packages

[](#packages)

Features that don't belong in core are available as official Vancil packages:

PackageDescription[`vancil/flint-auth`](https://github.com/Vancil/flint-auth)Auth scaffold — Bootstrap, Vue, and React presets with login, register, password reset, and email verification[`vancil/flint-mail`](https://github.com/Vancil/flint-mail)Mail package — Mailable classes, async queueing, and six drivers (SMTP, Mailgun, Postmark, SES, SendGrid, Log)Install any package with Composer and register it in your application — no configuration files to publish, no service providers to remember.

---

Getting Started
---------------

[](#getting-started)

```
composer create-project vancil/flint my-app
cd my-app
cp .env.example .env
# edit .env with your database credentials
php flint key:generate
php flint migrate
php -S localhost:8000 -t public
```

To scaffold auth pages and views immediately:

```
composer require vancil/flint-auth
php flint ui bootstrap --auth
php flint migrate
```

---

Directory Structure
-------------------

[](#directory-structure)

```
├── app/
│   ├── Controllers/
│   ├── Models/
│   └── Jobs/
├── config/
├── core/              # Framework internals (do not edit)
├── database/
│   └── migrations/
├── public/
│   └── index.php      # Single entry point
├── resources/
│   ├── views/         # Spark template files (.spark.php)
│   └── js/            # Frontend JS (Vue/React)
├── routes/
│   └── web.php
├── storage/
│   └── views/         # Compiled view cache (git-ignored)
└── flint              # CLI

```

---

Configuration
-------------

[](#configuration)

Copy `.env.example` to `.env` and fill in your values:

```
APP_NAME=Flint
APP_ENV=local
APP_DEBUG=true
APP_SECRET=change-me-in-production
APP_URL=http://localhost:8000

DB_DRIVER=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=flint
DB_USERNAME=root
DB_PASSWORD=

SESSION_COOKIE=flint_session
SESSION_LIFETIME=7200

MAIL_DRIVER=log
MAIL_FROM_ADDRESS=hello@example.com
MAIL_FROM_NAME="Flint"

QUEUE_DRIVER=database
```

Config files live in `config/`. Access values anywhere using dot notation:

```
config('app.name');        // "Flint"
config('database.driver'); // "mysql"
config('app.debug');       // true
```

---

Spark Template Engine
---------------------

[](#spark-template-engine)

Flint ships with **Spark**, a Blade-like template engine. View files use the `.spark.php` extension and live in `resources/views/`.

### Rendering a View

[](#rendering-a-view)

```
return Response::view('home', ['user' => $user]);
```

### Syntax

[](#syntax)

**Escaped output** (XSS-safe):

```
{{ $name }}

```

**Raw output:**

```
{!! $html !!}

```

**Control structures:**

```
@if ($user->isAdmin())
    Admin panel
@elseif ($user->isEditor())
    Editor panel
@else
    Welcome
@endif

@foreach ($posts as $post)
    {{ $post->title }}
@endforeach

```

**Auth directives:**

```
@auth
    Dashboard
@endauth

@guest
    Login
@endguest

```

**Forms:**

```

    @csrf

    @error('email')
        {{ $message }}
    @enderror
    Login

```

**Layouts:**

`resources/views/layouts/app.spark.php`:

```
>

{{ $title ?? 'Flint' }}

    @yield('content')

```

`resources/views/home.spark.php`:

```
@extends('layouts.app')

@section('content')
    Hello, {{ $name }}!
@endsection

```

**Partials:**

```
@include('partials.nav')
@include('partials.alert', ['type' => 'success', 'message' => 'Saved'])

```

### CLI

[](#cli)

```
php flint make:view auth.login     # resources/views/auth/login.spark.php
php flint make:layout app          # resources/views/layouts/app.spark.php
php flint view:clear               # delete compiled cache files
```

---

Sessions
--------

[](#sessions)

Sessions start automatically on every request (via global `SessionMiddleware`). Use the `session()` helper or inject `Flint\Session` directly:

```
session()->set('key', 'value');
session()->get('key', 'default');
session()->has('key');
session()->forget('key');
session()->flash('status', 'Saved successfully!');
```

**Old input** is available in views after a failed form submission:

```
old('email');        // PHP
@old('email')        // Spark directive
```

---

CSRF Protection
---------------

[](#csrf-protection)

All `POST`, `PUT`, `PATCH`, and `DELETE` requests are CSRF-verified automatically. Include the token in every form:

```

    @csrf
    ...

```

For AJAX requests, send the token as a header:

```
fetch('/api/data', {
    method: 'POST',
    headers: { 'X-CSRF-Token': await fetch('/api/csrf-token').then(r => r.json()).then(d => d.token) },
})
```

API routes under `/api/*` are excluded from CSRF verification by default. Customise via `config/csrf.php`:

```
return [
    'except' => ['/api/*', '/webhooks/*'],
];
```

---

Auth
----

[](#auth)

The `Flint\Auth\Auth` class provides session-based authentication. It is available via constructor injection:

```
use Flint\Auth\Auth;

class DashboardController
{
    public function __construct(private readonly Auth $auth) {}

    public function index(): Response
    {
        return Response::view('dashboard', ['user' => $this->auth->user()]);
    }
}
```

MethodDescription`$auth->user()`Authenticated user object, or `null``$auth->check()``true` if logged in`$auth->id()`Authenticated user's ID`$auth->guest()``true` if not logged in`$auth->login($user, $remember)`Log in; optionally set a 30-day remember-me cookie`$auth->logout()`End session and clear remember-me cookieProtect routes with the `auth` middleware alias:

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

To scaffold full auth pages (login, register, forgot password, email verification), install `vancil/flint-auth`.

---

Routing
-------

[](#routing)

Define routes in `routes/web.php`. The `$router` variable is available automatically.

```
$router->get('/users',         [UserController::class, 'index']);
$router->post('/users',        [UserController::class, 'store']);
$router->put('/users/{id}',    [UserController::class, 'update']);
$router->delete('/users/{id}', [UserController::class, 'destroy']);

// Closure routes
$router->get('/', fn() => Response::view('home'));
```

### Route Groups

[](#route-groups)

```
$router->group(['prefix' => '/api', 'middleware' => ['auth']], function ($router) {
    $router->get('/profile', [ProfileController::class, 'show']);
    $router->put('/profile', [ProfileController::class, 'update']);
});
```

### Route Parameters

[](#route-parameters)

```
// Route: /users/{id}
public function show(int $id): Response { ... }

// Route: /posts/{slug}
public function show(string $slug): Response { ... }
```

---

Controllers
-----------

[](#controllers)

```
php flint make:controller User
```

Controllers are plain classes in `app/Controllers/`. Dependencies are resolved via constructor injection.

```
namespace App\Controllers;

use Flint\Request;
use Flint\Response;
use App\Models\User;

class UserController
{
    public function index(): Response
    {
        return Response::view('users.index', ['users' => User::all()]);
    }

    public function store(Request $request): Response
    {
        $data = $request->validate([
            'name'  => 'required|string|max:255',
            'email' => 'required|email',
        ]);

        User::create($data);
        return Response::redirect('/users');
    }
}
```

---

Request
-------

[](#request)

```
$request->method();                  // "GET", "POST", etc.
$request->uri();                     // "/users/42"
$request->input('name');             // single value from GET/POST/JSON
$request->input('role', 'user');     // with default
$request->all();                     // all input merged
$request->has('email');              // bool
$request->header('Authorization');   // raw header value
$request->bearerToken();             // strips "Bearer " prefix
$request->isJson();                  // checks Content-Type
$request->file('avatar');            // $_FILES entry
$request->ip();                      // client IP
```

### Validation

[](#validation)

```
$data = $request->validate([
    'name'     => 'required|string|max:255',
    'email'    => 'required|email',
    'password' => 'required|min:8',
    'role'     => 'required|in:admin,editor,user',
    'score'    => 'nullable|numeric|min:0',
]);
```

On failure in a web (non-JSON) request, the user is redirected back with errors and old input automatically. In a JSON request, a `422` response is returned.

**Available rules:**

RuleDescription`required`Must be present and non-empty`string`Must be a string`int` / `integer`Must be an integer`numeric`Must be numeric`email`Must be a valid email`min:N`Length or value &gt;= N`max:N`Length or value &lt;= N`in:a,b,c`Must be one of the listed values`nullable`Allow null/missing (skips remaining rules)`confirmed`Must match `{field}_confirmation``unique:table,column`Must not already exist in the database---

Response
--------

[](#response)

```
Response::view('home', ['name' => 'Dan']); // render Spark view
Response::json($data, 200);                // application/json
Response::html('Hello');          // text/html
Response::text('plain text');              // text/plain
Response::redirect('/login', 302);         // redirect
Response::back();                          // redirect to previous URL
Response::noContent();                     // 204

// Web redirect helpers (chainable)
return Response::back()
    ->withErrors(['email' => ['Invalid credentials.']])
    ->withInput(['email' => $data['email']]);

// Header chaining
return Response::json(['id' => 1])
    ->withHeader('X-Custom', 'value')
    ->withStatus(201);
```

---

Mail
----

[](#mail)

Send email via the `Flint\Mail\Mailer` (injected via constructor):

```
use Flint\Mail\Mailer;

class WelcomeController
{
    public function __construct(private readonly Mailer $mailer) {}

    public function store(Request $request): Response
    {
        // ... create user ...

        $this->mailer
            ->to($user->email, $user->name)
            ->subject('Welcome to ' . config('app.name'))
            ->view('emails.welcome', ['user' => $user])
            ->send();

        return Response::redirect('/dashboard');
    }
}
```

Set the driver in `.env`:

```
MAIL_DRIVER=log    # writes to storage/logs/mail.log (default)
MAIL_DRIVER=smtp   # sends real email
```

---

Models &amp; ORM
----------------

[](#models--orm)

```
php flint make:model Post
```

```
namespace App\Models;

use Flint\Model;

class Post extends Model
{
    protected string $table = 'posts';
    protected array $fillable = ['title', 'body', 'user_id'];
    protected array $casts = ['published' => 'bool'];
}
```

### Querying

[](#querying)

```
Post::all();
Post::find(1);
Post::findOrFail(1);
Post::where('published', true)->get();
Post::where('score', '>', 4.5)->orderBy('created_at', 'DESC')->limit(10)->get();
Post::where('slug', $slug)->firstModel();
```

### Creating &amp; Updating

[](#creating--updating)

```
$post = Post::create(['title' => 'Hello', 'body' => '...']);
$post->update(['title' => 'Updated']);
$post->title = 'Also works';
$post->save();
$post->delete();
```

---

Migrations
----------

[](#migrations)

```
php flint make:migration create_posts_table
```

```
use Flint\Schema;
use Flint\Blueprint;

return new class {
    public function up(): void
    {
        Schema::create('posts', function (Blueprint $table) {
            $table->id();
            $table->integer('user_id')->unsigned();
            $table->string('title');
            $table->string('slug')->unique();
            $table->longText('body')->nullable();
            $table->boolean('published')->default(false);
            $table->datetime('published_at')->nullable();
            $table->timestamps();
        });
    }

    public function down(): void
    {
        Schema::dropIfExists('posts');
    }
};
```

```
php flint migrate
php flint migrate:rollback
```

### Schema Builder Reference

[](#schema-builder-reference)

MethodMySQL type`id()``BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY``string('col', 255)``VARCHAR(255)``text('col')``TEXT``longText('col')``LONGTEXT``integer('col')``INT``bigInteger('col')``BIGINT``boolean('col')``TINYINT(1)``float('col')``FLOAT``decimal('col', 8, 2)``DECIMAL(8,2)``datetime('col')``DATETIME``timestamp('col')``TIMESTAMP``json('col')``JSON``timestamps()`Adds `created_at` + `updated_at``softDeletes()`Adds `deleted_at`Modifiers: `->nullable()`, `->default($value)`, `->unique()`, `->unsigned()`

---

Middleware
----------

[](#middleware)

Built-in aliases registered automatically:

AliasBehaviour`cors`CORS headers + OPTIONS preflight (204)`session`Start session (applied globally)`csrf`Verify CSRF token on state-changing requests (applied globally)`auth`Redirect to `/login` if unauthenticatedApply per-route:

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

---

Cache
-----

[](#cache)

Configure the driver in `config/cache.php` or via `.env`:

```
CACHE_DRIVER=file   # file (default), redis, array
CACHE_TTL=3600      # default TTL in seconds
CACHE_PREFIX=flint_
```

Use the `cache()` helper or inject `Flint\Cache\Cache` directly:

```
cache()->put('key', $value);          // store with default TTL
cache()->put('key', $value, 60);      // store for 60 seconds
cache()->forever('key', $value);      // store indefinitely
cache()->get('key');                  // retrieve, null if missing
cache()->get('key', 'default');       // retrieve with fallback
cache()->has('key');                  // true/false
cache()->forget('key');               // remove one entry
cache()->flush();                     // remove all entries
cache()->pull('key');                 // get and immediately remove
```

`remember` retrieves a cached value or computes and stores it on a miss:

```
$user = cache()->remember('user.' . $id, 300, function () use ($id) {
    return User::find($id);
});

$settings = cache()->rememberForever('settings', fn() => Settings::all());
```

### Drivers

[](#drivers)

DriverDescription`file`Serialized files in `storage/cache/` — no extra dependencies`redis`Uses the `php-redis` extension; shares Redis config with the queue`array`In-memory only, no persistence — useful for testing```
php flint cache:clear   # flush all cached values
```

---

Queue
-----

[](#queue)

```
php flint make:job SendWelcomeEmail
```

```
namespace App\Jobs;

use Flint\Queue\Job;

class SendWelcomeEmail extends Job
{
    public int $tries = 3;

    public function __construct(
        private readonly int    $userId,
        private readonly string $email,
    ) {}

    public function handle(): void { /* ... */ }
    public function failed(\Throwable $e): void { /* ... */ }
}
```

```
use Flint\Queue\Queue;

Queue::dispatch(new SendWelcomeEmail($user->id, $user->email));
Queue::later(300, new SendWelcomeEmail($user->id, $user->email));
```

```
php flint queue:work
php flint queue:work --queue=emails
```

---

CLI Reference
-------------

[](#cli-reference)

```
php flint key:generate                 # generate APP_SECRET and write to .env
php flint make:controller        # app/Controllers/NameController.php
php flint make:model             # app/Models/Name.php
php flint make:job               # app/Jobs/NameJob.php
php flint make:migration         # database/migrations/_name.php
php flint make:view              # resources/views/.spark.php
php flint make:layout            # resources/views/layouts/.spark.php
php flint migrate                      # run pending migrations
php flint migrate:rollback             # roll back last batch
php flint queue:work                   # start queue worker
php flint cache:clear                  # flush all cached values
php flint view:clear                   # clear compiled Spark view cache
```

---

Error Handling
--------------

[](#error-handling)

SituationResponse`ValidationException` in JSON request`422` with `{ "errors": { ... } }``ValidationException` in web requestRedirect back with errors + old input`ModelNotFoundException``404` with error messageCSRF mismatch`419` plain textAny other `Throwable``500` — full trace if `APP_DEBUG=true`, generic message if false---

###  Health Score

40

—

FairBetter than 86% of packages

Maintenance89

Actively maintained with recent releases

Popularity3

Limited adoption so far

Community12

Small or concentrated contributor base

Maturity49

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 90% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~0 days

Total

2

Last Release

55d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/57faf02cb728789f5bb1242a73090ac1e9040ca9c10baf655e3a7e72d555d9ad?d=identicon)[SimpleVerify](/maintainers/SimpleVerify)

---

Top Contributors

[![danrovito](https://avatars.githubusercontent.com/u/8322674?v=4)](https://github.com/danrovito "danrovito (18 commits)")[![michael-mcmullen](https://avatars.githubusercontent.com/u/811123?v=4)](https://github.com/michael-mcmullen "michael-mcmullen (2 commits)")

###  Code Quality

TestsPHPUnit

### Embed Badge

![Health badge](/badges/vancil-flint/health.svg)

```
[![Health](https://phpackages.com/badges/vancil-flint/health.svg)](https://phpackages.com/packages/vancil-flint)
```

###  Alternatives

[tempest/framework

The PHP framework that gets out of your way.

2.2k34.4k16](/packages/tempest-framework)[showdoc/showdoc

ShowDoc is a tool greatly applicable for an IT team to share documents online

12.8k7.1k](/packages/showdoc-showdoc)[jasara/php-amzn-selling-partner-api

A fluent interface for Amazon's Selling Partner API in PHP

1348.7k1](/packages/jasara-php-amzn-selling-partner-api)[lion/bundle

Lion-framework configuration and initialization package

122.4k4](/packages/lion-bundle)[hardcastle/xrpl_php

PHP SDK / Client for the XRP Ledger

1110.7k7](/packages/hardcastle-xrpl-php)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
