Skip to content

Repository files navigation

⚡ Strife PHP Framework

A fast, lightweight PHP MVC framework designed for developers who prefer clean architecture, file-based routing, and a Vue.js-friendly front-end workflow.


Features

  • Smart Routing — File-based or explicit route definitions with full HTTP verb support.
  • Query Builder — Active record database layer with full isolation between models, transactions, seeding, and JSON backups.
  • Compiled View Engine — A blade-like template compiler that writes to disk cache (no eval()), with Vue-style component slots and layout composition.
  • Migration System — Generate, run, and rollback schema changes via CLI.
  • Yamato CLI — Code generation and database management from the command line.
  • Encryption & Hashing — Built-in tools for encoding strings.

Requirements

  • PHP 7.0 or higher
  • A web server (Apache or Nginx)
  • Composer

Installation

git clone https://github.com/strifejeyz/framework.git
cd framework
composer install

Copy and configure your environment:

# Edit app/config/application.php — set APP_NAME, APP_KEY, BASE_URL, etc.
# Edit app/config/database.php   — set host, database, username, password

Directory Structure

/
├── app/
│   ├── config/          # Application and database configuration
│   ├── controllers/     # Controller classes
│   ├── migrations/      # Database schema migration files
│   ├── models/          # Model classes extending QueryBuilder
│   ├── seeders/         # Database seeders
│   ├── views/           # Template files (.php)
│   └── routes.php       # Explicit route definitions
├── assets/
│   └── css/             # Public stylesheets
├── kernel/              # Framework core — do not modify
│   ├── database/        # QueryBuilder and connection drivers
│   ├── security/        # Encryption utilities
│   └── View.php         # Template engine (compiler + slots)
├── storage/
│   ├── cache/           # Compiled view cache
│   ├── backups/         # JSON database backups
│   └── logs/            # Application logs
├── index.php            # Application entry point
└── yamato               # CLI entry point

Configuration

Key constants in app/config/application.php:

Constant Default Purpose
APP_NAME 'Strife App' Application name used in views
DEV_MODE TRUE Enables dev-only tools — set FALSE in production
FILE_BASED_ROUTING true Toggle between file-based and explicit routing
CACHED_VIEWS FALSE Skip view recompilation on each request
MAINTENANCE_MODE FALSE Serves 503 to all requests when TRUE

Routing

File-Based Routing (Recommended)

Enable in app/config/application.php:

const FILE_BASED_ROUTING = true;

With file-based routing, URLs are automatically mapped to controllers and methods:

URL Resolves to
/home/index HomeController::index()
/book/show BookController::show()
/user/profile UserController::profile()

No manual route registration required. Any HTTP method is accepted.


Explicit Route Definitions

Set FILE_BASED_ROUTING = false and define routes in app/routes.php:

get('/users',          'UsersController@index');
post('/users/store',   'UsersController@store');
put('/users/update',   'UsersController@update');
patch('/users/modify', 'UsersController@modify');
delete('/users/remove','UsersController@destroy');

Tip: To use PUT, PATCH, or DELETE from an HTML form, add a hidden _method input field:

<form method="POST">
  <input type="hidden" name="_method" value="DELETE">
</form>

Named routes:

get('users-list -> /users', 'UsersController@index');

Route Priority

Specific (literal) routes always beat wildcard routes, regardless of the order they are defined in routes.php. The router automatically sorts routes by specificity before matching — so this is always safe:

get('/:any', 'HomeController@index'); // registered first — does NOT hijack /about-us
get('/about-us', 'HomeController@about');

/about-us correctly resolves to HomeController@about every time.


SPA Catch-All (Vue / React)

When pairing Strife with a frontend SPA, define a /:any catch-all to serve your app shell for any unmatched single-segment URL. Vue Router or React Router then handles client-side navigation:

// API routes first
get('/api/books',      'Api\BookController@index');
post('/api/books',     'Api\BookController@store');

// SPA shell — catch everything else and let the frontend router decide
get('/:any', 'HomeController@index');

Note: /:any only matches URLs with exactly one path segment. Multi-segment API routes like /api/books or /api/books/42 are never affected.


Dev Tools

Route Inspector

When DEV_MODE = TRUE, a route inspector is available at /_routes. It lists every registered endpoint with its HTTP method, URL pattern, handler, and route name. Filter by method or search by URL in real time.

Set DEV_MODE = FALSE before deploying to production — the /_routes endpoint is not registered at all when disabled.


Controllers

Create controllers in app/controllers/. Class names must be PascalCase and suffixed with Controller.

<?php

class BookController
{
    public function index()
    {
        $books = Book::get();
        return render('book', compact('books'));
    }

    public function show()
    {
        $book = Book::find($_GET['id']);
        return render('book/show', compact('book'));
    }
}

Generate a controller with Yamato:

php yamato create:controller Book

Models

Models live in app/models/ and extend Kernel\Database\QueryBuilder. Always specify the target table.

<?php

use Kernel\Database\QueryBuilder as Model;

class Book extends Model
{
    protected static $table = "books";
}

Generate a model with Yamato:

php yamato create:model Book books

Query Builder

Fetching

// Get all rows
$books = Book::get();

// Get the first row
$book = Book::first();

// Find by primary key
$book = Book::find(1);

// Find or throw an error
$book = Book::findOrFail(1);

Filtering

// Single condition
Book::where('author', '=', 'John')->get();

// OR condition
Book::where('author', '=', 'John')->orWhere('author', '=', 'Jane')->get();

// IN list
Book::whereIn('status', ['active', 'pending'])->get();

// Between two values
Book::whereBetween('created', $start, $end)->get();

// Check if any row matches
Book::where('title', '=', 'Dune')->exists(); // true/false

Selecting & Ordering

Book::select(['title', 'author'])
    ->where('author', '=', 'Frank Herbert')
    ->order('title', 'ASC')
    ->limit(10, 0)
    ->get();

Aggregates

Book::count();
Book::min('price');
Book::max('price');
Book::sum('price');
Book::avg('price');
Book::distinct('author')->get();

Insert

Book::insert([
    'title'  => 'Dune',
    'author' => 'Frank Herbert',
]);

// Insert, excluding certain fields
Book::insertExcept($data, ['csrf_token', 'submit']);

Update

// Update by primary key
Book::update(['title' => 'New Title'], $id);

// Increment / Decrement a column value
Book::where('id', '=', 1)->increment('views', 1);
Book::where('id', '=', 1)->decrement('stock', 5);

Delete

// Delete by condition
Book::where('id', '=', 1)->delete();

// Delete all rows
Book::delete();

Pull (Pluck single column)

// Single value
$title = Book::pull('title');

// Multiple columns as array
$info = Book::pull('title', 'author');

Joins

Book::join('authors')
    ->on('books.author_id = authors.id')
    ->get();

Transactions

Book::transact();

try {
    Book::insert(['title' => 'Draft']);
    Book::commit();
} catch (Exception $e) {
    Book::rollback();
}

Backup & Restore

// Exports table data to storage/backups/ as JSON
Book::backup();

// Restores from the most recent backup
Book::restore();

Important: Each model class maintains its own isolated query state. Using User::find(1) followed by Book::find(2) will not cause any result contamination between the two.


Views

Templates live in app/views/. The framework uses a compiled view engine — all custom syntax is converted to standard PHP and written to storage/cache/ as real files. No eval(). No OPCache penalty.

Render a View

// In a controller
return render('book', compact('books'));

// Equivalent full call
return View::render('book', compact('books'));

Template Engine

Layouts & Slots

Define your page shell in a single layout file (app/views/layouts/layout.php):

<!DOCTYPE html>
<html>
<head>
    <title>@show('title', 'Default Title')</title>
</head>
<body>
    <header>
        @show('header')
    </header>

    <main>
        @show
    </main>

    <footer>
        @show('footer', '<p>&copy; 2026</p>')
    </footer>
</body>
</html>

In your view file, wrap content with @layout / @endlayout and fill slots with @slot / @endslot:

@layout('layouts/layout')

  @slot('title')
    Books — My App
  @endslot

  @slot('header')
    <h1>Book Library</h1>
  @endslot

  {{-- Default slot: any content outside @slot tags goes into @show --}}
  <div class="content">
    <p>Hello from the main content area!</p>
  </div>

  @slot('footer')
    <p>Custom footer text</p>
  @endslot

@endlayout

Directive reference:

Directive Purpose
@layout('path/to/layout') Set the parent layout for this view
@endlayout Close the layout and trigger rendering
@slot('name') Start filling a named slot
@endslot End the named slot (resume default slot)
@show('name') Output a named slot in the layout
@show('name', 'fallback') Output slot or a fallback string if empty
@show Output the default (unnamed) slot

Outputting Variables

{{-- HTML-escaped output (safe) --}}
{{ $variable }}

{{-- Raw, unescaped output --}}
{! $variable !}

Conditionals

{if ($user->isAdmin())}
    <p>Welcome, admin.</p>
{elseif ($user->isEditor())}
    <p>Welcome, editor.</p>
{else}
    <p>Welcome, guest.</p>
{endif}

Loops

{foreach ($books as $book)}
    <li>{{ $book->title }}</li>
{endforeach}

{for ($i = 0; $i < 5; $i++)}
    <p>Item {{ $i }}</p>
{endfor}

{while ($condition)}
    <p>Looping...</p>
{endwhile}

Including Sub-Templates

{{-- Include and compile another template --}}
@render('partials/navbar')

{{-- Include a raw PHP file (no compilation) --}}
@get('partials/sidebar')

Escaping the @ Symbol

Since @ is the directive prefix, use @@ to output a literal @ in HTML:

<p>Contact us at info@@gmail.com</p>

Renders as:

<p>Contact us at info@gmail.com</p>

Legacy Layout System

The older @extend / @stop layout system is still supported for backward compatibility:

@extend('layouts/frontend')

<p>Page content here</p>

@stop()

Database Migrations

Generate a migration:

php yamato create:migration create_books_table books

Edit the generated file in app/migrations/, then run:

php yamato db:migrate   # Apply all pending migrations
php yamato db:rollback  # Undo all migrations

Yamato CLI

php yamato

Generators

Command Description
php yamato create:controller Name Create a new controller
php yamato create:model Name table Create a new model
php yamato create:migration Name table Create a migration file
php yamato create:key Generate a random application key

Database

Command Description
php yamato db:migrate Run all migrations
php yamato db:rollback Rollback all migrations
php yamato db:backup Backup table data to JSON
php yamato db:restore Restore from last backup
php yamato db:seed Run database seeders

Utilities

Command Description
php yamato clear:logs Delete log files
php yamato clear:cache Delete compiled view cache
php yamato clear:all Delete logs, cache, and backups
php yamato hash:encode string Hash a string
php yamato encryption:encode string Encrypt a string
php yamato encryption:decode string Decrypt a string

Contributing

  1. Fork the repository.
  2. Create a new branch for your feature or bugfix.
  3. Submit a pull request with a clear description of your changes.

License

Strife is open-source software licensed under the MIT License.

Support

For questions or issues, open a ticket on GitHub Issues.

Releases

Packages

Used by

Contributors

Languages