gecole / framework
Gecole Framework — a small, dependency-free PHP framework: container, router, HTTP kernel, sessions, auth, ORM and query builder, migrations, plain-PHP views, validation, console and logging.
Requires
- php: >=8.3
- ext-curl: *
- ext-json: *
- ext-mbstring: *
- ext-openssl: *
- ext-pdo: *
Requires (Dev)
None
Suggests
- gecole/cache-redis: Adds a Redis cache store (optional package, keeps the core dependency-free)
- illuminate/view: Renders .blade.php templates through the Blade view engine adapter
- latte/latte: Renders .latte templates through the Latte view engine adapter
- mustache/mustache: Renders .mustache templates through the Mustache view engine adapter
- twig/twig: Renders .twig templates through the Twig view engine adapter
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-09-22 18:14:09 UTC
README
A small, dependency-free PHP framework: container, HTTP kernel, router, sessions, auth, ORM and query builder, migrations, views (plain PHP plus optional Twig/Blade/Latte/Mustache adapters), validation, console and logging.
It has no runtime Composer dependencies — only PHP 8.3+ and a handful of
extensions. Everything else (the .NET-style API client, the monitoring
tooling, the frontend pipeline of a given application) belongs to the
application that uses it.
composer require gecole/framework
What is in the box
| Area | Classes | Notes |
|---|---|---|
Foundation | Application, Container, Config, Env, ErrorHandler, ServiceProvider | auto-wiring container, .env reader, dot-notation config, error/exception rendering |
Http | Kernel, Request, Response, JsonResponse, RedirectResponse, Pipeline, Middleware, Client | route resolution, middleware stack, cURL transport (form posts, multipart, retries, TLS options) |
Routing | Router, Route, RouteLoader | parameters, optional segments, where() constraints, groups with prefix/name/middleware, named-route URLs, 404/405, loading web.php/api.php with prefix + middleware + name attributes |
Session / Auth | Session, Csrf, SessionGuard, UserProvider, Authenticatable | flash data, old input, CSRF tokens, session-backed guard with a pluggable user provider |
Database | Connection, QueryBuilder, Migrations\Migrator, Migrations\Migration, Migrations\Seeder, Schema\SchemaBuilder, Schema\Blueprint, Orm\Model, Orm\ModelQuery, relations | PDO wrapper with a query log, bound-parameter query builder, additive migrations, small ORM (HasMany, BelongsTo, BelongsToMany) |
View | ViewFactory, ViewEngine, Engines\PhpEngine, Engines\TwigEngine, Engines\BladeEngine, Engines\LatteEngine, Engines\MustacheEngine, AssetManager | plain-PHP templates by default; engine chosen by file suffix (php, twig, blade.php, latte, mustache), layouts, sections, stacked view paths, custom engines; content-hashed assets from a build manifest with un-hashed fallbacks |
Validation | Validator | rule strings (required\|string\|min:2\|max:191, url, json, in:, alpha_dash, …) |
Console | Console, Command, Input, Output | command registry/dispatcher, tables, colour, quiet mode, capturable output |
Logging | Logger | daily/single file channels, levels, in-memory ring buffer, tail() for a log screen |
Support | Arr, Str, Json, Clock, helpers.php | app(), config(), env(), base_path()/storage_path()/view_path()…, e(), route(), url(), asset(), csrf_field(), session(), auth(), request(), response(), view(), now() |
Bootstrapping an application
The whole framework is started from one file: an Application over a project
root, which loads config/, .env, base bindings, then the service providers
listed in config/app.php.
// bootstrap/app.php
require dirname(__DIR__) . '/vendor/autoload.php';
$app = new Gecole\Framework\Foundation\Application(dirname(__DIR__));
$app->bootstrap();
return $app;
// public/index.php
$app = require dirname(__DIR__) . '/bootstrap/app.php';
$request = Gecole\Framework\Http\Request::capture();
$response = $app->handle($request);
$response->send();
$app->terminate($request, $response);
// bin/console
$app = require dirname(__DIR__) . '/bootstrap/app.php';
exit($app->container()->make(Gecole\Framework\Console\Console::class)->run($argv));
Console already knows route:list, serve, migrate, migrate:status,
migrate:rollback and db:seed; an application registers its own commands on
top of those.
Views and view engines
ViewFactory resolves a dotted view name (pages.about) against one or more
view paths and picks the first matching engine by file suffix:
| Suffix | Engine | Package |
|---|---|---|
.php | built-in plain PHP ($view is the factory) | — |
.twig | Twig | twig/twig |
.blade.php | Blade | illuminate/view |
.latte | Latte | latte/latte |
.mustache | Mustache | mustache/mustache |
The adapter libraries are optional: they are only referenced when a template
with that suffix is rendered, and a missing package raises a message saying what
to require. An engine can also be named explicitly
($views->render('pages.about.twig'), $views->render('pages.about.blade.php')),
and a project can register its own:
$app->views()->extend('tpl', fn ($views) => new MyEngine($views->paths()));
$app->views()->use('tpl'); // default for names without a suffix
app.views.default, app.views.paths and the per-adapter options are read from
config/app.php (see the starter for a complete example). Each adapter exposes
the framework helpers to its templates (route(), asset(), config(),
csrf_field(), …); Blade additionally registers @csrf, @route and @asset,
and every engine receives the factory as $view.
API routes
A route file is plain PHP that returns
function (Router $router, Application $app): void. RouteLoader loads it inside
a group, so an API file does not repeat its prefix, middleware or name prefix:
$loader = new Gecole\Framework\Routing\RouteLoader($app->router(), $app);
$loader->load($app->basePath('routes/web.php'), ['middleware' => 'web']);
$loader->load($app->basePath('routes/api.php'), [
'prefix' => 'api',
'middleware' => 'api',
'name' => 'api.',
]);
app.middleware_groups defines the named stacks the kernel expands when it
builds the pipeline, so web (session + CSRF) and api (stateless) stay
separate. Requests under /api/ that raise an error are rendered as a JSON
envelope by ErrorHandler.
More capability, still dependency-free
Recognisable patterns have been folded in without adding dependencies:
- Events —
Gecole\Framework\Events\Dispatcher, wildcard/interface listeners, and theapplication.booted,request.receivedandrequest.handledlifecycle events (event(),event_listen()). - Cache & filesystem —
app()->cache()/cache()over an Array or File store (app.cache), andapp()->files()for project I/O. The cache manager is extensible:CacheManager::addDriver()(orapp.cache.drivers) registers any backend, and optional packages plug in without the core depending on them (e.g.gecole/cache-redisadds aredisdriver). - Auth guards —
AuthManagerresolves named guards;tokenauthenticates aBearertoken againstpersonal_access_tokens(auth('token')), on top of the sessionwebguard. - Route model binding —
Route::model('user', User::class)+ theSubstituteBindingsmiddleware hand the controller a model instead of an id. - Validation — database-aware
uniqueandexistsrules via a connection resolver. Custom rules are extensible without core changes: implement theGecole\Framework\Validation\Rulecontract (or pass a callable) and register it withValidator::extend('name', …)from a service provider (parameters after the colon are passed through).make:rulescaffolds one. - Middleware —
TrimStrings,HandleCors(with kernel-answered preflight),ThrottleRequestsandAuthenticate. - Console generators —
make:controller|model|migration|command|provider|middleware|service|traitandapp:key, scaffolding namespaced files underapp.paths.
Layout the framework expects
The layout is a convention, and every path is overridable — set
app.paths.<name> in config/app.php (relative values resolve against the
project root, absolute ones are used as given):
| Path | Method | Default | Override |
|---|---|---|---|
| project root | basePath() | — | constructor argument |
| config | configPath() | config/ | app.paths.config |
| application classes | appPath() | src/App/ | app.paths.app |
| database | databasePath() | database/ | app.paths.database |
| migrations | migrationPath() | database/migrations/ | app.paths.migrations |
| seeders | seederPath() | database/seeders/ | app.paths.seeders |
| public | publicPath() | public/ | app.paths.public |
| resources | resourcePath() | resources/ | app.paths.resources |
| views | viewPath() | resources/views/ | app.paths.views |
| storage | storagePath() | storage/ | app.storage_path |
Everything else — providers, middleware, session, logging, assets, server,
pagination — is read from the application's config/app.php; see the starter
package for a complete example.
Configuration keys the framework reads
| Key | Purpose |
|---|---|
app.name, app.env, app.debug, app.timezone, app.locale | identity of the application; debug decides how much an error response reveals |
app.providers | service providers, register()d in order then boot()ed |
app.middleware | global middleware, outermost first |
app.middleware_groups | named middleware stacks (web, api, …) a route references by name |
app.views | default view engine, extra view paths, per-adapter options |
app.api | prefix/middleware/name used when loading routes/api.php (starter convention) |
app.session | cookie name, lifetime, cookie options |
app.logging | enabled, channel (daily/single), level, filename, requests |
app.assets | manifest, live, fallbacks |
app.server | host/port/workers for serve |
app.paths, app.storage_path | directory overrides |
database.default, database.connections.*, database.migrations_table | PDO connections and migration bookkeeping |
auth.provider | the UserProvider class the session guard authenticates against |
The framework reads database.migrations_table (default migrations), so an
application sharing a database with a legacy system can keep its bookkeeping in
its own table.
Deliberate choices
- No runtime dependencies. No Composer packages are required to boot; the only requirement is PHP itself. Bundling is the application's business.
- Errors are always rendered. With
app.debugthe response carries the exception class, file, line and a trimmed trace; without it, a generic message and a line instorage/logs. Anerrors.errorview is used when the application provides one, otherwise the framework answers with a self-contained HTML page. API paths get a JSON envelope. - Explicit over magic. Bindings are declared in
registerBaseBindings(), routes in a route file, middleware in config — nothing is discovered by scanning directories at runtime.
Internationalisation
A dependency-free translator ships with French and Arabic support and a clear
way to add more. Lines live in resources/lang/{locale}.php as dot-notation
arrays (resources/lang/fr.php → return ['home' => ['title' => 'Accueil']]),
and app.locales lists the supported codes (app.locales_rtl marks
right-to-left ones, ar by default). Translating is a global call:
'Cart has %count items' => echo trans('cart.items', ['count' => 2]);
trans($key, $replace, ?$locale)/__()substitute:placeholdervalues and fall back toapp.fallback_localebefore returning the key unchanged.trans_choice()pluralises Laravel-style|segments ({0}…|[1,Inf]…).translator()/app()->translator()exposelocale(),setLocale(),hasLocale(),locales(),direction()(rtl/ltr) andchoice().SetLocalemiddleware (in thewebgroup) picks the locale from?lang=, the session, orAccept-Language, persists it, and shareslocale,dirandlocalesto the views. Views can usetrans()/__()directly, and the plain-PHP/Twig/Blade/Latte/Mustache engines expose them too.- End users add a language by writing
resources/lang/xx.phpand addingxxtoapp.locales— no code changes.
Publishing core migrations
Core features that need a table (such as API token auth) do not alter the
database automatically. They ship migration templates under the framework's
resources/migrations, and publish:migrations copies the ones you want into
database/migrations as timestamped files:
php bin/console publish:migrations # all core migrations
php bin/console publish:migrations tokens # just the personal_access_tokens table
php bin/console publish:migrations --list # what is available
php bin/console migrate # apply them like any migration
You can edit the published files; they run, roll back and report through the
normal migrate/migrate:rollback/migrate:status commands.
Tests
The suite is dependency-free as well (php tests/run.php), and it boots a
fixture application in tests/fixtures/app that uses nothing but the framework:
composer install
php tests/run.php # everything
php tests/run.php http # only cases whose path matches
php tests/run.php --stop # stop at the first failure
Coverage: container/config/paths, router matching, route files + middleware
groups, query-builder SQL, validation rules, support helpers, view rendering
(engine resolution, custom engines, stacked paths, asset fallbacks), the
middleware pipeline, the HTTP kernel end to end (routing → middleware → view,
error rendering, 404/405, JSON errors), the console (command list, route:list,
help, output degradation) and the database layer against SQLite (schema
building, migrations, the query builder and the ORM).
Consumers
gecole/starter— a runnable skeleton application plus a scaffolder.