strifejeyz / framework
A Fast and Lightweight PHP MVC Framework.
Requires
- php: >= 7.0
- phpmailer/phpmailer: ^6.3
This package is not auto-updated.
Last update: 2026-08-23 22:19:25 UTC
README
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, orDELETEfrom an HTML form, add a hidden_methodinput 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:
/:anyonly matches URLs with exactly one path segment. Multi-segment API routes like/api/booksor/api/books/42are 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 byBook::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>© 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
- Fork the repository.
- Create a new branch for your feature or bugfix.
- 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.