mawsis/nebula-php

There is no license information available for the latest version (v1.0.0) of this package.

A PHP 8 MVC framework built from scratch: DI container, router, ORM, migrations, middleware, validation, and JWT auth

Maintainers

Package info

github.com/Mawsis/NebulaPHP

pkg:composer/mawsis/nebula-php

Transparency log

Statistics

Installs: 21

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v1.0.0 2025-02-12 23:26 UTC

This package is auto-updated.

Last update: 2026-07-13 03:55:51 UTC


README

Nebula is a PHP 8 MVC framework written from scratch to understand how modern frameworks actually work under the hood (dependency injection, routing, an ORM, middleware, validation) without hiding any of it behind magic. The entire core is ~2,600 lines across 50 focused classes with only three runtime dependencies (dotenv, Monolog, firebase/php-jwt), so it is small enough to read in an afternoon and real enough to build a JSON API or a server-rendered app with. It is installable via Composer as mawsis/nebula-php.

Features

  • DI container: bind / singleton / instance / alias, with reflection-based auto-wiring for constructors, controller methods, and route closures
  • Router: static and {param} routes, closure or [Controller::class, 'method'] callbacks, per-route middleware
  • ORM: active-record style DbModel (findOne, create, save) on top of a fluent QueryBuilder (where / joins / groupBy / having / orderBy / limit / offset, all with bound parameters), plus hasOne / hasMany / belongsTo relationships and eager loading via with()
  • Migrations: file-based migrations with an applied-migrations table and rollback support
  • HTTP layer: Request with input sanitization, JSON body parsing, and helpers (input, only, except, bearerToken); Response with a consistent JSON envelope and success / error / validationError helpers
  • Validation: Laravel-style form requests: declare rules() on a Request subclass, use string rules ('min:8', 'unique:users:email') or rule objects, call validated()
  • Auth: session-based authentication with a configurable user model, plus stateless JWT (HS256) via JwtHelper
  • Middleware: Auth, JWT, CORS, CSRF, and JSON middleware included; custom middleware is a single execute() method
  • Error handling: typed exceptions (NotFoundException, UnauthorizedException, ForbiddenException, ValidationException, ...) mapped by a central handler to JSON for API requests or rendered error views
  • Extras: facades (Route, DB, Auth, Session, Logger), a Transform layer for shaping API resources, pagination, flash messages, Monolog logging, dotenv config

Requirements

  • PHP >= 8.0
  • Composer

Quick Start

Scaffold a new application with the Nebula Installer:

composer global require mawsis/nebula-installer
nebula new my-app

Or pull the core into an existing project:

composer require mawsis/nebula-php

A minimal front controller looks like this:

// public/index.php
require_once __DIR__ . '/../vendor/autoload.php';

Dotenv\Dotenv::createImmutable(dirname(__DIR__))->safeLoad();
Nebula\Core\Config::load(dirname(__DIR__) . '/config');

$app = new Nebula\Core\Application(dirname(__DIR__));

require_once __DIR__ . '/../routes/main.php';

$app->run();

See Nebula-Example for a complete application skeleton (config, providers, routes, controllers, migrations, views, Docker setup).

Usage

Routing

Routes take a closure or a [Controller::class, 'method'] pair, plus an optional list of middleware aliases:

use Nebula\Core\Facades\Route;
use App\Controllers\AuthController;
use App\Controllers\UserController;

Route::get('/users', [UserController::class, 'listUsers'], ['auth']);
Route::get('/users/{id}', [UserController::class, 'showUser'], ['auth']);
Route::post('/login', [AuthController::class, 'loginStore']);

Controller methods are resolved through the container, so dependencies are injected by type hint:

public function listUsers(Request $request, Response $response)
{
    $users = Paginator::paginate(User::query()->orderBy('id', 'ASC'), $response);

    return $response->json(['users' => $users]);
}

Models and queries

A model declares its table, attributes, and primary key; the base class does the rest:

namespace App\Models;

use Nebula\Core\DbModel;

class Post extends DbModel
{
    public static function tableName(): string  { return 'posts'; }
    public static function primaryKey(): string { return 'id'; }
    public static function attributes(): array  { return ['title', 'body', 'user_id']; }

    public function user()
    {
        return $this->belongsTo(User::class, 'user_id');
    }
}
$post = Post::findOne(['id' => 42]);

$posts = Post::query()
    ->where('user_id', '=', $userId)
    ->orderBy('created_at', 'DESC')
    ->limit(10)
    ->get();

$posts = Post::with(['user'])->where('title', 'LIKE', '%nebula%')->get(); // eager loading

Post::create(['title' => 'Hello', 'body' => '...', 'user_id' => 1]);

Validation

Declare rules on a Request subclass. String rules are resolved through config/validations.php, and anything after : is passed to the rule's constructor; you can also pass rule objects or write your own by extending BaseValidation:

namespace App\Requests;

use Nebula\Core\Request;

class RegisterRequest extends Request
{
    public function rules(): array
    {
        return [
            'username' => ['required', 'min:3', 'max:20'],
            'email'    => ['required', 'email', 'unique:users:email'],
            'password' => ['required', 'min:8'],
        ];
    }
}

Type-hint the request in a controller action and call validated(); on failure it throws a ValidationException, which the central handler turns into a 422 JSON response with per-field errors:

public function registerStore(RegisterRequest $request, Response $response)
{
    $data = $request->validated();

    User::create([
        'username' => $data['username'],
        'email'    => $data['email'],
        'password' => password_hash($data['password'], PASSWORD_DEFAULT),
    ]);

    return $response->success(['registered' => true], 201);
}

Middleware

Middleware aliases are defined once in config/middlewares.php and referenced by name in routes:

return [
    'auth' => Nebula\Core\Middlewares\AuthMiddleware::class,
    'jwt'  => Nebula\Core\Middlewares\JwtMiddleware::class,
    'csrf' => Nebula\Core\Middlewares\CsrfMiddleware::class,
    'cors' => Nebula\Core\Middlewares\CorsMiddleware::class,
    'json' => Nebula\Core\Middlewares\JsonMiddleware::class,
];

A custom middleware is one class with one method; it halts the request by throwing a typed exception:

use Nebula\Core\Facades\Auth;
use Nebula\Core\Middlewares\BaseMiddleware;
use Nebula\Core\Exceptions\ForbiddenException;

class AdminMiddleware extends BaseMiddleware
{
    public function execute()
    {
        if (Auth::isGuest() || !Auth::user()->is_admin) {
            throw new ForbiddenException();
        }
    }
}

Design

Everything in Nebula is deliberately hand-rolled to expose the mechanics that larger frameworks abstract away:

  • Container (Container): a static service container with bind / singleton / instance / alias. Auto-wiring uses reflection to resolve constructor, controller-method, and closure dependencies, with sensible fallbacks for defaults and nullable parameters.
  • Router (Router + Route facade): routes are stored per HTTP method; {param} segments are compiled to regexes at match time. Route middleware is resolved from config aliases (or passed as instances) and executed before the action.
  • ORM (DbModel + QueryBuilder): models describe their schema in three static methods; the query builder assembles parameterized SQL and hydrates results back into model instances. Relationships are plain methods built on the same query builder, and with() eager-loads them onto results.
  • HTTP + errors (Request, Response, Handler): requests sanitize input and parse JSON bodies; responses emit a consistent {success, status_code, data | error} envelope. A central handler maps typed exceptions to JSON for API requests or rendered error views, and fatal errors get a dedicated error page.
  • Validation: rule classes implement a two-method BaseValidation contract (validate, getErrorMessage); string rules map to classes through config, so applications can register their own rules the same way the built-ins work.
  • Auth: a session-backed Auth service (login, logout, user, isGuest) with the user model class supplied via config, plus JWT issuance/verification for stateless APIs.
  • Facades: a ~10-line __callStatic base class that proxies to container-resolved services, which is all a facade actually is.

Ecosystem

Repository Description
Nebula-Installer nebula new CLI for scaffolding new applications
Nebula-Example Full example application built on the framework

License

Released under the MIT License.