A way fast PHP project builder
Maxiter is a custom PHP framework built around simple file-based conventions, fast bootstrapping, and backward-compatible development workflows. It is not Laravel, although some API routing ergonomics intentionally feel familiar.
The framework has two main runtime flows: classic page routing and API routing. It also ships with a custom CLI entrypoint through php maxiter for scaffolding, migration helpers, testing, and developer utilities.
env.ini (INI format) and .env (dotenv KEY=VAL format).php maxiter serve [port] [host] command with built-in file watcher, same-tab auto-reload, static-vs-PHP split strategy (HMR-style broadcast for CSS/JS/HTML, restart for PHP), WebSocket + SSE dual transport, overlay status badge, cross-platform native PHP streams only, multi-client broadcast, single-tab-open guard.-c preview / dry-run, -f safe move, -r restore, -d permanent delete), flags accepted in any position, 80+ detection rules covering env files, dev-server flags, tests, docs, CI/CD configs, IDE folders, code-quality tools, logs, and temp files; full stats output with byte counters.realpath() path-traversal protection on static HTML serving.$browserOpened flag).If you want an AI assistant to follow Maxiter conventions closely, use the dedicated specialist prompt available here or inside your git clone folder.
Maxiter favors runtime-derived paths and lightweight setup. You can run it on older environments, but framework internals should still be treated carefully whenever compatibility-sensitive files are changed.
git clone https://github.com/maxiter-php/maxiter.git
Clone the framework from GitHub, or create a new project with Composer if that fits your workflow better.
composer create-project maxiter/maxiter:dev-main
cd maxiter
After entering the project directory, serve it from a local web root such as htdocs, www, or a Docker volume.
php maxiter
The maxiter file is the framework command hub. Use it to scaffold controllers, APIs, models, middleware, views, unit tests, and migration helpers.
LoadModel.php is the central runtime bootstrapper. If you modify bootstrap behavior, validate carefully because classic controllers and API execution may depend on the current load order.
If your project uses Composer, keep the autoload step available. If not, make sure manual class loading still matches the framework conventions.
Maxiter has two major request paths:
The main front controller is index.php. It starts the session when needed, reads $_GET['url'], and decides whether the request should be handled as a classic page or delegated into the API flow.
Maxiter keeps routing file-based and direct. Controllers live in app/controllers, reusable logic stays in app/models, middleware lives in app/middlewares, and pages live under resources/views/pages.
Backward compatibility matters. Older Maxiter projects may still depend on legacy controller execution patterns, so framework changes should prefer additive behavior over disruptive rewrites.
Main front controller for page requests and API delegation.
API bootstrap and dispatcher entrypoint.
Folder that stores API route definition files.
Classic controllers and API controllers.
Core framework models and project business logic.
Middleware classes used by the API router.
Page folders, page PHP files, and page-specific CSS and JS assets.
Template scaffolding source used by generation workflows.
Custom CLI entrypoint.
Main framework configuration file. Maxiter uses env.ini, not a modern .env-style setup.
Local development GUI tooling. Treat them as development helpers, not production runtime files.
Page requests start in index.php. If the first URL segment is not api, Maxiter resolves a page file using the pattern below:
resources/views/pages/<page>/<page>.php
Examples:
/home -> resources/views/pages/home/home.php
/login -> resources/views/pages/login/login.php
Missing pages are redirected to ./error.
Shared layout fragments usually live in folders such as _header, _footer, _navbar, _sidenav, and _cards.
/resources/views/pages
/_header
header.php
/_footer
footer.php
/home
/css
home.css
/js
home.js
home.php
<?php require __DIR__ . "/../../../../app/models/LoadModel.php"; ?>
<?php include __DIR__ . "/../_header/header.php"; ?>
<div class="container">
<h2>Home Page</h2>
</div>
<script src="./resources/views/pages/home/js/home.js"></script>
<?php include __DIR__ . "/../_footer/footer.php"; ?>
New work should keep the page folder pattern because it is part of the framework developer experience.
When the first URL path segment is api, index.php stores the parsed route array in $_SESSION['api-route'] and forwards execution to routes/api.php.
ApiModel::reset();
ApiModel::loadRoutesFromDirectory(__DIR__ . '/api');
ApiModel::dispatch($urlParsed);
reset() — empties the route registry and group stack.loadRoutesFromDirectory() — loads every *.php file
inside routes/api/ in ascending alphabetical order
(sorted by filename).dispatch($urlParsed) — runs the router: extracts path and HTTP
method, iterates registered routes in order, executes the first match,
returns JSON 404 if nothing matches.
Route registration is api-prefix-free. The router automatically strips the
leading /api or /api/ from the incoming request before
matching:
GET /api → routed as /GET /api/users → matched against a route registered as /users
So in your route files you never put the /api/ prefix
yourself.
API route definition files live in routes/api/*.php and are loaded
automatically. You can also register routes directly in
routes/api.php itself — that's how the default scaffolded routes live.
Every HTTP-verb helper (get/post/put/patch/delete/any/match) and every
router entry accepts three possible formats for the $action argument.
Internally resolved by ApiModel::resolveAction():
| Format | Example | Where it works best |
|---|---|---|
| Array tuple (RECOMMENDED default) | array('MyController', 'methodName') |
Explicit and framework-default style — used by the bundled
routes/api.php examples. |
| String method name | 'showUser' |
Only when registered inside a
ApiModel::controller('X', ...) group. The controller name is
inherited from the group. |
| String controller name | 'DashboardController' |
Outside a controller group: the method falls back to main()
automatically. |
All HTTP-verb helpers accept an optional third parameter for middleware. Accepts either a single string or an array of middleware class names.
// routes/api.php (project defaults — recommended tuple style)
ApiModel::get('/system/info', array('ApiTestController', 'info'));
ApiModel::get('/system/greeting', array('ApiTestController', 'helloWorld'));
// Single middleware on one endpoint
ApiModel::get('/auth/me', array('ApiTestController', 'withMiddleware'),
'BearerAuthorizationMiddleware');
// Full CRUD example (per-route inline action tuples)
ApiModel::get('/users', array('ApiTestController', 'indexUsers'));
ApiModel::post('/users', array('ApiTestController', 'storeUser'));
ApiModel::get('/users/{id}', array('ApiTestController', 'showUser'));
ApiModel::put('/users/{id}', array('ApiTestController', 'updateUser'));
ApiModel::delete('/users/{id}', array('ApiTestController', 'destroyUser'));
// Nested dynamic parameters
ApiModel::get('/posts/{postId}/comments/{commentId}',
array('ApiTestController', 'showPostComment'));
// Group: controller inherited -> second arg is just the method name as a string
ApiModel::controller('ApiTestController', function () {
ApiModel::get('/info', 'info');
ApiModel::get('/users/{id}', 'showUser');
ApiModel::get('/posts/{p}/comments/{c}','showPostComment');
});
The API router exposes the following methods on ApiModel.
Every verb helper has the same signature:
method($url, $action, $middleware = null).
| Method | Signature / behavior |
|---|---|
get / post / put / patch / delete | Single HTTP verb. |
any($url, $action, $mw = null) | Matches any HTTP verb. |
match($methods, $url, $action, $mw=null) | Matches the list of verb strings passed as first array, e.g. match(['GET','HEAD'], ...). |
group($attributes, $callback) | Generic group with an associative attributes array. Recognized keys: 'prefix', 'controller', 'middleware' (string or array). Groups can be nested. |
prefix($prefix, $callback) | Shorthand for group(['prefix' => ...], ...). |
controller($name, $callback) | Shorthand for group(['controller' => ...], ...) — inside this group a string action is treated as the method name. |
middlewareGroup($mw, $callback) | Shorthand for group(['middleware' => ...], ...). |
reset() | Empties routes and group stack. |
loadRoutesFromDirectory($dir) | Loads all *.php files inside the dir, A-Z sorted. |
dispatch($urlParsed = null) | Runs the request dispatch loop. |
route($string, $url, $controller, $fn, $mw=null) | Legacy one-shot router (see below). |
Route parameters use the {paramName} syntax. Names must start with a
letter or underscore and contain letters, digits, or underscores only. Parameters are
matched by a single URL segment each (slashes / and dots .
terminate the capture; captured values are automatically
urldecode()'d.
Parameter injection into controller methods uses
ReflectionMethod by parameter NAME — not by position.
The behavior is:
{id} and the controller method signature has a
parameter called $id, the matched value is injected there.
If ReflectionMethod is unavailable (very old PHP), the router falls
back to call_user_func_array() with route values in declaration order.
When a route matches, the router executes the following well-defined sequence:
$GLOBALS['maxiter_secure_controller_context']) containing the
controller, function name, and route parameters — used by
SecureRequestModel and the classic request protections.require_once app/controllers/<Name>.php
(skipped if already autoloaded by Composer).array_unique).public static function handle() returning a truthy value.new ControllerName().ob_start()) and invokes the controller action
with parameters resolved by name.ResponseModel::json(true, $result) — producing the standard
Maxiter { success, data } envelope.ApiModel::route()Still available exclusively for backward compatibility with very old Maxiter projects. Its signature is:
ApiModel::route($string, $url, $controller, $function, $middleware = null);
Behavior: it performs an immediate normalized-path equality check.
If $string and $url do not match it sends JSON 404
and aborts the entire request — never tries other routes. This is
not a registration method; it's a direct dispatch. Use the modern
helpers above for all new routes.
Maxiter stores application settings in either of two files - both are
fully supported and both accept the keys ENVIRONMENT or
APP_ENV to toggle dev vs production mode. You only need one
of them in your project root.
Classic Maxiter format with section groups.
[app]
APP_NAME="Maxiter"
APP_DESCRIPTION="Custom PHP framework"
BEARER_TOKEN="your-token"
ENVIRONMENT="development"
[timezone]
DEFAULT_TIMEZONE="America/Sao_Paulo"
[maxiter]
DB="maxiter"
DRIVER="mysql"
PORT="3306"
HOST="localhost"
USER="root"
PASS=""
Laravel/Symfony-style dotenv file, with comment lines (# or
;) and optional quoted values.
# App meta
APP_NAME="Maxiter"
APP_ENV=development
APP_DESCRIPTION="Custom PHP framework"
BEARER_TOKEN="your-token"
DEFAULT_TIMEZONE="America/Sao_Paulo"
# Database
DB=maxiter
DRIVER=mysql
PORT=3306
HOST=localhost
USER=root
PASS=""
Environment detection order:
env.ini → .env → OS env vars APP_ENV /
ENVIRONMENT. Values dev, development,
and local enable development-only features (the live-reload Dev
Server, script injection, and internal __maxiter_live_* routes). Any other
value automatically enables production-safe guardrails (all dev features disabled, no
output-buffer overhead, path-traversal protection on static HTML serving).
echo EnvModel::env("APP_NAME");
$result = DatabaseModel::connection(EnvModel::env("DB"))
->execute("SELECT * FROM users WHERE id = :id", array(
":id" => 11
));
$data = $result->fetch(PDO::FETCH_ASSOC);
ResponseModel::json(true, $data);
Maxiter no longer depends on a manually configured APP_BASE_URL. Base URL, base path, scheme, and host are inferred at runtime by AppUrlModel. New code should prefer AppUrlModel::url() and AppUrlModel::asset().
Maxiter keeps reusable runtime logic in app/models. The models below are the most important ones to understand before changing framework behavior.
LoadModel.php is the framework bootstrapper. It starts the session if needed, loads Composer autoload when present, requires core models, sets the timezone from env.ini, and initializes CORS.
require __DIR__ . "/../models/LoadModel.php";
If you change bootstrapping, review both classic controllers and API requests because they often depend on this file directly.
AuthModel is the framework helper for authentication and authorization. It commonly checks session state and optional authorities.
if (AuthModel::verify()) {
ResponseModel::json(true, "Authenticated");
}
if (AuthModel::verify(null, array("administrator"))) {
ResponseModel::json(true, "Authorized");
}
User context can be read through AuthModel::getContext() when login logic stores the expected session data.
AppUrlModel dynamically resolves scheme, host, base path, and base URL. It replaced the older hardcoded base URL approach and should be preferred for new links and asset generation.
echo AppUrlModel::baseUrl();
echo AppUrlModel::url("login");
echo AppUrlModel::asset("resources/views/pages/home/css/home.css");
Do not reintroduce a static base URL unless the project explicitly requires that behavior.
ResponseModel standardizes JSON and HTTP responses in controllers and API endpoints.
ResponseModel::json(true, array("status" => "ok"));
ResponseModel::json(false, "Unauthorized access", 401);
ResponseModel::http(404);
API controllers often return arrays or strings directly, but ResponseModel::json() remains the clearest way to send explicit JSON responses.
SecureRequestModel protects classic controller requests by validating trusted request origin data such as HTTP_HOST, HTTP_X_FORWARDED_HOST, HTTP_ORIGIN, and HTTP_REFERER.
require __DIR__ . "/../models/LoadModel.php";
require __DIR__ . "/../models/SecureRequestModel.php";
Other important helpers include CorsModel, TreatModel, PagesTitleModel, and LogModel. They keep cross-cutting concerns out of controllers and preserve framework conventions.
Maxiter ships with a framework CLI accessed through php maxiter. It focuses on file-system-oriented commands and simple developer workflows.
Creates a classic controller in app/controllers/.
php maxiter new controller UsersController
Creates a new model in app/models/.
php maxiter new model Payments
Creates a new page/view structure following the framework page conventions.
php maxiter new view Login
Creates a log model example using the informed database connection name.
php maxiter new log maxiter
Creates a table SQL example in the framework source area for known reference tables.
php maxiter new table users
Creates an API controller in app/controllers/ and a route file in routes/api/ with a default route based on the controller name.
php maxiter new api ApiUsersController
Creates a middleware class in app/middlewares/. Middleware classes expose public static function handle().
php maxiter new middleware BearerAuthorizationMiddleware
Generates a unit test scaffold for a controller method.
php maxiter new unittest UsersController userLogin
Maxiter ships with a built-in PHP development server and file watcher. It behaves like modern JS framework dev servers (Vite, React, Angular, etc.): you edit files, the browser reloads automatically in the same tab - no manual F5 and no new tabs are opened on every change.
$browserOpened guard)
php maxiter serve [port] [host]
php maxiter serve 8080
php maxiter serve 8080 0.0.0.0
php maxiter serve --watch
The WebSocket port is HTTP port + 1000. For example,
php maxiter serve 8080 serves HTTP on port 8080 and the
live-reload channel on port 9080.
Additional developer utilities include:
php maxiter server [port]
php maxiter gui
php maxiter pathcheck
The GUI and helper pages are intended for local development workflows.
Sets the base path used by the project when you need to define it manually for a local environment.
php maxiter path http://localhost/my-project/
Applies a template from src/template/[template_name] to the current project structure.
php maxiter new template admin-dashboard
Opens the Maxiter GUI helper in the browser for local development tasks and quick project actions.
php maxiter gui
Runs the PHPUnit test suite after project dependencies are installed.
composer install
php maxiter testme
config prod generates a production-oriented ignore configuration.
deltoprod is the actual non-production file cleanup CLI, rewritten in
v1.8.1 with four safe operating modes. Flags are accepted in
any position in the command line.
| Short flag | Long flag | Mode | Description |
|---|---|---|---|
-c |
--check |
Preview / dry-run | Always run this first. Shows every file/dir that would be affected with reasons, sizes, and full stats. NO FILES ARE CHANGED. |
-f / --move |
--force |
Safe move | Moves files to _non-prod-files/ folder. Can be restored later.
Recommended before every deploy. |
-r |
--restore |
Restore backup | Restores everything from _non-prod-files/ back to the project
root. Existing files are renamed with a ~restored_bak_TIMESTAMP
suffix before overwriting. |
default / -d |
--delete |
Permanent delete | Removes files permanently. Shows a big red warning banner. Use only after
verifying with -c. |
# Always preview first (nothing changes)
php maxiter deltoprod -c
php maxiter -c deltoprod
# Then the recommended safe deploy flow
php maxiter config prod
php maxiter deltoprod -f
# Undo the previous step (restore)
php maxiter deltoprod -r
# Permanent delete only after double-checking
php maxiter deltoprod
php maxiter deltoprod -d
env.ini, .env and every common variant (.env.example, .env.production, .env.dev, .env.local, ...).maxiter_dev_server flag file, glob patterns _test_*.php, test_*.php, *_test.php, _*_*.phptests/, test/, Tests/, __tests__/, spec/, features/, phpunit.xml, .phpunit.result.cache, coverage/README*, maxiter.md, CHANGELOG.md, CONTRIBUTING.md, release_notes.txt, docs/, documentation/.travis.yml, .github/, .circleci/, Dockerfile*, docker-compose*, Makefile, build/.vscode/, .idea/, .editorconfigphpcs.xml, phpmd.xml, psalm.xml, phpstan.neon, infection.json.dist.gitignore*, .gitattributes*.log, error_log, access_log, debug.log, app.log, *.tmp, *.bak, *.swp, *.swo, *~_non-prod-files/ folder itself in delete modeGenerates a reusable component file and can optionally use a template-defined component block.
php maxiter new component searchbox
php maxiter new component searchbox mytemplate
Attempts to detect and configure the application base path automatically for the local environment.
php maxiter autopath
php maxiter autopath 7000
Exports or imports database mirror files using the configured connection name from env.ini.
php maxiter mirror maxiter export
php maxiter mirror maxiter import
php maxiter mirror maxiter import 01-01-2001
Returns the current path configuration used by the application.
php maxiter pathcheck
Switches route/controller execution mode between older and newer project styles when a legacy project requires that behavior.
php maxiter cro
php maxiter crn
Migrates an older Maxiter-based project into the current framework version. It validates the source path, copies pages and controllers, copies middleware when present, copies only missing model files, and backs up overwritten files into src/versioning_backup/<timestamp>/.
php maxiter versioning "C:/legacy-project"
Scans models, controllers, and middleware for syntax or features incompatible with the specified PHP version and writes a report to src/codeversion/<version>/verification-<timestamp>.txt.
php maxiter codeversion 5.3
The generated report should list the affected file, affected line, incompatibility reason, and offending code line only.
Template scaffolding source lives in src/template/. Generated pages and front-end assets continue to follow the resources/views and resources/views/pages/<page>/ conventions.
resources/views/pages/<page>/css/
resources/views/pages/<page>/js/
For new work, prefer runtime-aware helpers such as AppUrlModel::url() and AppUrlModel::asset() instead of hardcoded paths.
Keep scaffolding straightforward. Maxiter favors simple developer-facing file patterns over abstraction-heavy template systems.
Compatibility-sensitive internals should stay friendly to older PHP versions, especially when the area is meant to remain compatible with PHP 5.3.
Avoid:
- short arrays []
- ::class
- scalar type hints
- return types
- nullable types
- arrow functions
- typed properties
- enums
Classic controller access relies on SecureRequestModel. API security can be extended through middleware such as bearer authorization handlers.
LoadModel initializes CORS. Session startup also needs to remain safe because the API flow depends on session state before dispatching the stored route.
Treat gui.html and bash.php as local development tools. Review carefully before exposing them in production environments.
index.php
routes/api.php
app/models/LoadModel.php
app/models/ApiModel.php
app/models/AppUrlModel.php
app/models/EnvModel.php
app/models/ResponseModel.php
app/models/AuthModel.php
app/models/SecureRequestModel.php
maxiter
bash.php
gui.html
Preserve current runtime behavior unless a redesign is explicitly requested. Keep routing file-based, keep scaffolding predictable, and prefer small framework-consistent changes.
Always clarify whether the work affects page routing, API routing, CLI, models, or generated scaffolding. Also call out PHP-version and backward-compatibility impact whenever framework internals are involved.