Bootstrap & Routing
How the framework loads itself and routes requests to controllers
Setting Up the Framework
After sessions start, the framework needs to prepare itself. This is called "bootstrapping" - getting ready to handle requests.
Think of it like opening a restaurant:
- Turn on the lights (load helpers)
- Prepare the kitchen (set up database)
- Print the menu (load routes)
- Open the doors (start accepting requests)
Loading Helper Functions
require BASE_PATH . ('framework/Core/functions.php');This file defines global helper functions you can use anywhere:
view('welcome.view.php'); // Render a view
redirect('/dashboard'); // Redirect to a URL
abort(404); // Stop with an error
csrf_token(); // Get CSRF token
old('email'); // Get old form input
base_path('routes/routes.php'); // Get absolute pathWhy Global Functions?
These are used so frequently that typing \Core\Helpers::view() would be tedious. Global functions make code cleaner:
// Without helpers
\Core\View::render('welcome.view.php', ['name' => 'Ali']);
// With helpers
view('welcome.view.php', ['name' => 'Ali']);The Trade-off
Pros:
- Cleaner, more readable code
- Faster to type
- Familiar to Laravel developers
Cons:
- Can conflict with other libraries
- Harder to track where functions come from
- Not namespaced
DALT.PHP chooses convenience here, which is fine for a learning framework.
Bootstrapping the Framework
require base_path('framework/Core/bootstrap.php');This file does the heavy lifting:
1. Load Environment Variables
$dotenv = Dotenv\Dotenv::createImmutable(base_path(''));
$dotenv->safeLoad();Reads .env file and fills $_ENV:
DB_DRIVER=sqlite
DB_DATABASE=database/app.sqlite
APP_DEBUG=trueNow you can access: $_ENV['DB_DRIVER']
2. Load Configuration Files
$dbConfig = require base_path('config/database.php');Config files return arrays:
return [
'database' => [
'driver' => $_ENV['DB_DRIVER'] ?? 'sqlite',
'database' => $_ENV['DB_DATABASE'] ?? 'database/app.sqlite',
]
];3. Create the Dependency Container
$container = new Container();A container is a "service registry" - a place to store and retrieve shared objects.
Think of it like a toolbox:
- You put tools in once
- Anyone can grab them when needed
- No need to create duplicates
4. Bind the Database
$container->bind('Core\Database', function () use ($dbConfig) {
return DatabaseManager::create($dbConfig['database']);
});This says: "When someone asks for Core\Database, run this function to create it."
The database isn't created yet - just the recipe for creating it.
5. Make Container Globally Available
App::setContainer($container);Now anywhere in your code:
$db = App::resolve(Database::class);Loading the Platform (Optional)
if (is_dir(base_path('.dalt')) && file_exists(base_path('.dalt/bootstrap.php'))) {
require base_path('.dalt/bootstrap.php');
}What is .dalt?
The .dalt folder is the learning platform - the course UI, lessons, and challenges.
Key insight: It's completely optional.
- If
.daltexists → you get the course at/learn - If
.daltis deleted → framework still works perfectly
This separation is brilliant:
- Core framework is independent
- Learning features don't pollute the core
- You can remove
.daltfor production
Creating the Router
$router = new \Core\Router();The router is the "map" from URLs to controllers:
GET /posts → posts/index.php
GET /posts/10 → posts/show.php
POST /login → session/store.phpLoading Routes
require base_path('routes/routes.php');
if (is_dir(base_path('.dalt')) && file_exists(base_path('.dalt/routes/routes.php'))) {
require base_path('.dalt/routes/routes.php');
}User Routes First
Your app routes are loaded first:
// routes/routes.php
global $router;
$router->get('/', 'welcome.php');
$router->get('/posts', 'posts/index.php');Platform Routes Second (If Exists)
Then .dalt routes are added:
// .dalt/routes/routes.php
$router->get('/learn', 'learn/index.php');
$router->get('/learn/lessons/{lesson}', 'learn/lesson.php');This means:
- Your routes have priority
- Platform adds its own routes without conflicts
- Both can coexist
Capturing the Request
$request = Request::capture();This creates a Request object from PHP superglobals:
// Instead of:
$uri = $_SERVER['REQUEST_URI'];
$method = $_POST['_method'] ?? $_SERVER['REQUEST_METHOD'];
$input = $_POST['email'];
// You can use:
$uri = $request->path();
$method = $request->method();
$input = $request->input('email');Why Wrap Superglobals?
Benefits:
- Cleaner API
- Easier to test (you can mock Request)
- Handles edge cases (like method spoofing)
- Type hints work better
Binding Request to Container
App::bind(Request::class, fn () => $request);Now controllers can get the request:
$request = App::resolve(Request::class);Routing the Request
$uri = $request->path();
$method = $request->method();
$router->route($uri, $method, $request);What Happens Inside route()
- Loop through registered routes
- Match method (GET, POST, etc.)
- Match URI pattern (including
{id}params) - Run middleware (auth, csrf, etc.)
- Inject route params into
$_GET - Require the controller file
The controller file runs and generates the response.
Key Takeaways
- Bootstrap prepares the framework - Loads config, creates container, binds services
- Container manages shared objects - Database, Request, etc.
- Routes map URLs to controllers - Simple and explicit
- Platform is optional -
.daltcan be removed without breaking the core - User routes have priority - Your app routes load first
What's Good Here
✅ Clear separation: core vs platform
✅ Container pattern for dependency injection
✅ Routes are explicit and easy to understand
✅ Request object wraps superglobals cleanly
✅ Lazy loading of database (only created when needed)
✅ Safe .env loading (doesn't crash if file missing)
Next, we'll look at error handling and how validation errors flow through the system.