Welcome to the official documentation for the PHP Framework β a modern, lightweight, and developer-friendly web application framework designed for speed, simplicity, and scalability.
This guide will walk you through installation, configuration, and usage of the framework, along with its key components and features.
This project uses a modern containerized architecture powered by Docker, combining application code, databases, caching, queues, and local AWS emulation.
Languages & Frameworks
Infrastructure & Services
- MySQL 8 β Relational database
- phpMyAdmin β Database management UI
- Redis (Alpine) β Cache & queue driver
- Memcached (Alpine) β Alternative caching layer
- Mailhog β Local email testing (SMTP + Web UI)
- Cron β Scheduled tasks runner
- LocalStack β Local AWS services (S3, SQS, SNS, Lambda, DynamoDB)
This setup provides a full-featured development environment that mirrors production as closely as possible, while staying lightweight and developer-friendly.
Install the framework using Composer:
composer create-project roy404/framework project-name Once installed, navigate to your project root directory and start the local development server:
php artisan serve Make sure your system meets the following minimum requirements:
- PHP β₯ 8.2
- Composer (for dependency management)
- Docker & Docker Compose (for containerized development)
If PHP or Composer are not installed yet, follow these steps:
sudo apt install php php-cli php-mbstring unzip curl -yor on macOS:
brew install phpInstall Composer
curl -sS https://getcomposer.org/installer | php
sudo mv composer.phar /usr/local/bin/composerVerify installation:
php -v
composer -V- Download PHP from: https://windows.php.net/download/
- Add the PHP folder to your system PATH.
- Download Composer from: https://getcomposer.org/download/
- Run the installer and follow the prompts.
After installation, open Command Prompt or PowerShell and verify:
php -v
composer -VIf these commands return version numbers, both are installed correctly.
Additionally, If you want to use Docker just install it here: https://www.docker.com/products/docker-desktop
Before running the project, configure your environment settings.
-
Copy the example file if needed:
cp .env.example .env -
Update the values as needed β especially:
- APP_URL (e.g. http://localhost:8000)
- Database credentials
- Mail settings
- AWS or LocalStack credentials (if applicable)
- PROJECT_ID β make sure this value is unique for each project to avoid Docker container name conflicts.
Example:- Project 1 β
PROJECT_ID=myproject1 - Project 2 β
PROJECT_ID=myproject2
- Project 1 β
Start the development environment using Docker:
docker-compose up --build -dOnce all containers are running, you can access the following services:
| Service | URL | Description |
|---|---|---|
| π§± App (PHP) | http://localhost:8000 | Main application |
| π phpMyAdmin | http://localhost:8080 | MySQL database manager |
| π MailHog UI | http://localhost:8025 | View test emails sent from the app |
| π§ Redis | localhost:6379 | In-memory cache database |
| π§° Memcached | localhost:11211 | Caching service |
| βοΈ LocalStack | http://localhost:4566 | Local AWS cloud service emulator |
Tip: You can check logs for any service using
docker-compose logs -f <service_name>
Example:
docker-compose logs -f appTo stop and remove all running containers:
docker-compose downIf you also want to remove associated volumes and networks (fresh start):
docker-compose down -v- Mailer / SMTP errors: Ensure MAIL_HOST is set to
mailhog(not container_name) when used inside Docker. - DB connection issues: Ensure DB_HOST is
mysqland the MySQL container is healthy (docker-compose ps/docker-compose logs mysql). - Ports already in use: Another project may be using the same host ports (e.g., 8000, 8080, 8025).
β ChangeAPP_PORT,PMA_PORT, or other port values in.env.
- Rebuild & recreate containers:
docker-compose up --build -d - Stop & remove containers:
docker-compose down - Stop, remove containers + volumes:
docker-compose down -v - Follow logs:
docker-compose logs -f - Run a shell inside the app container:
docker-compose exec app sh - Connect to MySQL (from host):
mysql -h 127.0.0.1 -P 3306 -u -p
β
Tip for multiple projects:
If you run multiple Docker projects at once, always give each one a unique PROJECT_ID and different port numbers to avoid conflicts.
Example:
- Project A β
PROJECT_ID=framework1,APP_PORT=8000,PMA_PORT=8080 - Project B β
PROJECT_ID=framework2,APP_PORT=8100,PMA_PORT=8180
Your development environment comes preconfigured with the following services:
- app β The main PHP application container (
/var/www/html). - mysql β MySQL database service.
- phpmyadmin β Web interface for managing MySQL (
http://localhost:8080). - memcached β Caching service to speed up application performance.
- mailhog β SMTP testing tool with a web UI at (
http://localhost:8025). - redis β In-memory data store for caching, queues, and sessions.
- cron β Dedicated service for running scheduled tasks.
- localstack β Local AWS cloud emulator (S3, SNS, SQS, etc).
- xdebug β Debugging and profiling extension for PHP, integrated into the
appcontainer. - socket β Real-time communication service powered by Node.js and Socket.IO, running on port
3000(http://localhost:3000).
The framework provides a modular architecture with the following core components.
Routes are the entry points of your application. They define how incoming HTTP requests (such as GET, POST, PUT, or DELETE) are mapped to specific actions in your code β usually a function or a controller method.
Think of them as a map:
- The URL (e.g.,
/users/5) - The HTTP method (e.g.,
GET) - The action (what your app should do, like showing a user profile)
Define routes in the routes/web.php file:
use App\Routes\Route;
use Handler\Controller\UserController;
Route::get('/', function () {
return view('welcome');
});
Route::get('/users/{id}', [UserController::class, 'show']);The framework provides a fluent, expressive API for defining routes.
Below are the available static methods for configuring routes:
| Method | Description |
|---|---|
put(string $uri, string|array|Closure $action = []) |
Defines a PUT route. |
patch(string $uri, string|array|Closure $action = []) |
Defines a PATCH route. |
delete(string $uri, string|array|Closure $action = []) |
Defines a DELETE route. |
get(string $uri, string|array|Closure $action = []) |
Defines a GET route. |
post(string $uri, string|array|Closure $action = []) |
Defines a POST route. |
group(array $attributes, Closure $action) |
Registers a group of routes with shared configurations and middleware, enhancing route organization and reusability. |
controller(string $className) |
Registers a controller to handle the route actions. |
middleware(string|array $action) |
Assigns middleware to the route for request filtering. |
prefix(string $prefix) |
Adds a URI prefix to all routes in the group. |
name(string $name) |
Assigns a name to the route, useful for generating URLs. |
domain(string|array $domain) |
Binds the route to a specific domain or subdomain. |
where(string $key, string $expression) |
Defines a regular expression constraint for a route parameter. |
The Scheduler provides a route-like API for defining and managing recurring tasks using cron expressions.
Instead of handling raw cron jobs directly, you define schedules in a clean and expressive way β similar to defining routes.
All schedules are defined in the routes/cron.php file:
use App\Console\Schedule;
Schedule::command('clear:logs')->daily();
Schedule::command('emails:send')->everyFiveMinutes();
Schedule::command('report:generate')->at('14:30');The scheduler provides expressive helpers for defining task frequency:
| Method | Description |
|---|---|
everyMinute() |
Run the task every minute. |
everyFiveMinutes() |
Run the task every 5 minutes. |
hourly() |
Run the task every hour. |
daily() |
Run the task daily at midnight. |
weekly() |
Run the task weekly on Sunday at midnight. |
monthly() |
Run the task monthly on the 1st at midnight. |
yearly() |
Run the task yearly on January 1st at midnight. |
cron($expression) |
Use a custom cron expression (e.g., 0 6 * * 1-5). |
at('HH:MM') |
Run the task daily at a specific time. |
Middlewares are responsible for filtering and processing HTTP requests before they reach your controllers or route logic. They can be used for authentication, authorization, input validation, logging, or modifying responses.
You can define your middleware in the Handler/Middleware directory:
namespace Handler\Middleware;
use App\Utilities\Request;
class Account
{
/**
* Handle an incoming request.
*
* This method is automatically invoked before a route or controller is executed.
* You can perform validation, authentication, or any pre/post request logic here.
*
* @param Request $request
* @return mixed
*/
public function handle(Request $request)
{
// Example pre-processing: authentication/validation
if (!$this->authorize($request)) {
return redirect('/unauthorized', 403);
}
$response = 'ok!';
// Example post-processing: logging, response modification
$this->log($request, $response);
// Return true to continue request processing
return true;
}
/**
* Perform authorization/validation logic.
*/
protected function authorize(Request $request): bool
{
// Default: allow all requests
return true;
}
/**
* Example logging or post-processing.
*/
protected function log(Request $request, mixed $response): void
{
// You could log request details here
}
}Example Usage
To register your middleware, open app/Routes.php and attach it to a route group:
'web' => [
'middleware' => [
Handler\Middleware\Account::class
]
],This ensures that every request under the web group passes through the Account middleware before rendering the final page.
Controllers are responsible for handling request/response logic. They act as an intermediary between your routes and your business logic, keeping your code organized and maintainable.
You can define controllers in the Handler/Controllers directory:
namespace Handler\Controller;
use App\Http\Controller;
use App\Utilities\Request;
class UserController extends Controller
{
public function show(Request $request, int $id)
{
return view('users.show', ['user' => User::find($id)]);
}
}Example Usage:
Route::get('/users/{id}', [Handler\Controller\UserController::class, 'show']);Models represent your database tables and provide an abstraction layer for querying and manipulating records.
Each model maps to a database table and defines the structure of its data.
Example Model:
namespace Handler\Model;
use App\Databases\Facade\Model;
class User extends Model
{
public string $primary_key = 'id';
public string $table = 'users';
public array $fillable = ['id', 'name', 'email'];
}Models provide a simple, expressive interface for database operations.
Insert a new record:
$newUser = Handler\Model\User::create([
'name' => 'John Doe',
'email' => 'john.doe@test.com',
'status' => 1
]);Fetch active users:
$users = Handler\Model\User::where('active', 1)->fetch();Check if a record exists:
$isExist = Handler\Model\User::where('id', $userId)->exists();Fetch a single column (by primary key):
$email = Handler\Model\User::_($userId)->email;For advanced queries, you can use the Database facade directly.
Insert a record:
App\Databases\Database::create('users', [
'name' => 'John Doe',
'email' => 'john.doe@test.com',
'status' => 1
]);Run a raw query:
$users = App\Databases\Database::query("SELECT * FROM users");Count total records:
$total = App\Databases\Database::table('users')->count();Query from a specific connection:
$users = App\Databases\Database::server('master')
->query("SELECT * FROM users")
->fetch();The Schema Builder provides a programmatic way to create, modify, and manage database tables.
It works with closures to define table blueprints and runs SQL queries under the hood.
use App\Databases\Schema;
use App\Databases\Handler\Blueprints\Table;
Schema::create('users', function (Table $table) {
$table->id();
$table->string('name');
$table->string('email')->unique();
$table->timestamps();
});use App\Databases\Schema;
use App\Databases\Handler\Blueprints\Table;
Schema::table('users', function (Table $table) {
$table->string('phone')->nullable();
});use App\Databases\Schema;
// Rename table
Schema::renameTable('users', 'members');
// Drop table if it exists
Schema::dropIfExists('sessions');
// Drop table permanently
Schema::drop('logs');use App\Databases\Schema;
// Check if table exists
Schema::hasTable('users');
// Get column info
Schema::column('users', 'email');
// Fetch all columns
Schema::fetchColumns('users');
// Drop a column
Schema::dropColumn('users', 'phone');
// Rename a column
Schema::renameColumn('users', 'fullname', 'name', 'VARCHAR(255)');use App\Databases\Schema;
// Add index
Schema::addIndex('users', 'email');
// Drop index
Schema::dropIndex('users', 'email_index');
// Fetch index details
Schema::index('users', 'email_index');use App\Databases\Schema;
// Change storage engine
Schema::setEngine('users', 'InnoDB');
// Change character set and collation
Schema::setCharset('users', 'utf8mb4', 'utf8mb4_unicode_ci');
// Truncate a table
Schema::truncate('users');// Get CREATE TABLE statement for export/backup
$definition = App\Databases\Schema::exportTable('users');β
With Schema, you can define migrations, manage schema changes, and keep your database structure consistent across environments.
The framework uses the Blade templating engine, which provides a clean and expressive syntax for building your views. Blade templates are compiled into plain PHP and cached for optimal performance.
{{-- Escaped output (HTML is escaped) --}}
<p>{{ '<strong>This will not render as bold</strong>' }}</p>
{{-- Unescaped output (HTML will render) --}}
<p>{!! '<strong>This will render as bold text</strong>' !!}</p>
{{-- Conditional statements --}}
@if($isAdmin)
<p>You have admin access.</p>
@elseif($isUser)
<p>You are logged in as a regular user.</p>
@else
<p>Please log in to continue.</p>
@endif
{{-- Loops --}}
<ul>
@foreach($tasks as $task)
<li>{{ $loop->iteration }}. {{ $task }}</li>
@endforeach
</ul>
{{-- Including other templates --}}
@include('partials.footer')
{{-- Components --}}
<x-alert type="success" message="Welcome to the app!" />| Directives | Description | Example |
|---|---|---|
{{ $var }} |
Escaped output | {{ $user->name }} |
{!! $html !!} |
Unescaped output (renders HTML) | {!! $post->content !!} |
@if / @elseif / @else / @endif |
Conditional logic | @if($user) ... @endif |
@foreach / @endforeach |
Loop through an array or collection | @foreach($items as $item) ... @endforeach |
@for / @endfor |
Basic for loop | @for($i = 0; $i < 5; $i++) ... @endfor |
@include('layout') |
Include the path content | @include('header') |
@csrf |
Insert a CSRF token for forms | <form>@csrf</form> |
@php / @endphp |
PHP Tags | @php $test = "foo"; @endphp |
@post |
Grab the POST Global Variable | @post('email') |
The framework includes a powerful command-line interface called Artisan, designed to help you perform common development tasks quickly β such as running servers, managing migrations, creating files, and clearing caches.
You can run Artisan commands using:
php artisan [command]# Start the local development server
php artisan serve# Display a list of all available commands
php artisan listBuild reactive, stateful UI components with StreamWire β without writing any JavaScript.
StreamWire integrates seamlessly with Blade templates, allowing you to render dynamic components directly in your views.
namespace Components;
use App\Utilities\Handler\Component;
class Counter extends Component
{
public $count = 0;
public function increment()
{
$this->count++;
}
/**
* @see ./views/components/counter.blade.php
*/
public function render()
{
return $this->compile([
'count' => $this->count
]);
}
}Example content ./views/components/counter.blade.php:
<div class="container">
<h1>{{ $count }}</h1>
<button wire:click="increment()">+</button>
</div>
You can embed reactive components directly inside your Blade views.
Simply call the stream() helper function and pass the component class.
Example:
<div class="container">
{!! stream(Components\Counter::class) !!}
</div>
This will render the Counter component and make it fully interactive without writing any JavaScript.
| Directives | Description | Example |
|---|---|---|
wire:model |
Two-way data binding between input fields and component properties | <input type="text" wire:model="name"> |
wire:click |
Trigger an action method on click | <button wire:click="save()">Save</button> |
wire:submit |
Listen for form submission and call a method | <form wire:submit="register()">...</form> |
wire:keydown.keypress |
Trigger a method on a specific keypress event | <input wire:keydown.keypress="search(event.target.value)"> |
wire:keydown.enter |
Trigger an action method when Enter is pressed | <input wire:keydown.enter="submitForm()"> |
wire:keydown.escape |
Run a method when Escape is pressed | <input wire:keydown.escape="resetForm()"> |
wire:loader |
Show or hide elements or more while a request is processing | <div wire:loader.classList.add="active">Loading...</div> |
To support real-time updates such as live notifications, chat, or dashboard syncing, this project includes a dedicated Socket.IO microservice built with Node.js and integrated into the Docker environment.
- Real-time bi-directional communication
- WebSocket-based events (auto fallback to polling)
- JSON-based event messaging
- Centralized logging for connections and events
To scaffold a ready-made Socket.IO service within your project, run:
php artisan make:socketThis command generates a preconfigured Node.js Socket.IO setup inside your project directory (/node by default).
Next, register the Socket service in your docker-compose.yml file:
node:
build:
context: ./node
dockerfile: Dockerfile
container_name: ${PROJECT_ID}_socket
restart: unless-stopped
ports:
- "${SOCKET_PORT:-3000}:3000"
volumes:
- "./storage/logs/node:/usr/src/app/logs"
networks:
- project_network
env_file:
- .envOnce added, rebuild and start your containers:
docker-compose up --build -d This will build and launch the Socket.IO container, making it accessible at
π http://localhost:3000
You can verify the socket connection by embedding the following script in your Blade or HTML view:
(function () {
const script = document.createElement("script");
script.src = "https://cdn.socket.io/4.7.5/socket.io.min.js";
script.onload = () => {
const socket = io("http://localhost:3000");
socket.on("connect", () => {
console.log("β
Connected:", socket.id);
socket.emit("message", "Hello from frontend");
});
socket.on("message", (msg) => {
console.log("π¬ From server:", msg);
});
};
document.head.appendChild(script);
})();When you reload the page, open your browser console β you should see messages confirming a successful connection between the frontend and the Socket.IO server.
The project includes a built-in task scheduler for handling automated and recurring jobs (e.g., queue processing, cleanups, reports).
In a Docker environment, the scheduler container runs automatically β no additional configuration is required.
However, in a production environment, youβll need to register the scheduler manually in your system crontab to ensure it runs every minute:
* * * * * /usr/bin/php /var/www/html/artisan cron:scheduler >> /dev/null 2>&1 * * * * *β Run every minute./usr/bin/phpβ Path to your PHP binary (check withwhich php)./var/www/html/artisanβ Path to your project'sartisanfile.>> /dev/null 2>&1β Silences all output (keeps system logs clean).
Use LocalStack to emulate AWS services locally during development. This allows you to test S3 storage and other AWS features without connecting to a real AWS account.
Create a new bucket:
docker exec -it localstack_container awslocal s3 mb s3://my-bucket List existing buckets:
docker exec -it localstack_container awslocal s3 ls π‘ Note: LocalStack is intended only for local development and testing. On a production server, you must configure real AWS credentials and services (e.g., using IAM roles, S3 buckets, etc.).
Contributions are welcome! Please fork the repository and submit a pull request.
This framework is open-source software licensed under the MIT license.