Search by

gecole / framework

ecoleplus

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.

Package info

gitlab.com/gecole/framework

Issues

pkg:composer/gecole/framework

Statistics

Installs: 14

Dependents: 1

Suggesters: 0

Stars: 0

dev-main 2026-09-22 18:14 UTC

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

AreaClassesNotes
FoundationApplication, Container, Config, Env, ErrorHandler, ServiceProviderauto-wiring container, .env reader, dot-notation config, error/exception rendering
HttpKernel, Request, Response, JsonResponse, RedirectResponse, Pipeline, Middleware, Clientroute resolution, middleware stack, cURL transport (form posts, multipart, retries, TLS options)
RoutingRouter, Route, RouteLoaderparameters, 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 / AuthSession, Csrf, SessionGuard, UserProvider, Authenticatableflash data, old input, CSRF tokens, session-backed guard with a pluggable user provider
DatabaseConnection, QueryBuilder, Migrations\Migrator, Migrations\Migration, Migrations\Seeder, Schema\SchemaBuilder, Schema\Blueprint, Orm\Model, Orm\ModelQuery, relationsPDO wrapper with a query log, bound-parameter query builder, additive migrations, small ORM (HasMany, BelongsTo, BelongsToMany)
ViewViewFactory, ViewEngine, Engines\PhpEngine, Engines\TwigEngine, Engines\BladeEngine, Engines\LatteEngine, Engines\MustacheEngine, AssetManagerplain-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
ValidationValidatorrule strings (required\|string\|min:2\|max:191, url, json, in:, alpha_dash, …)
ConsoleConsole, Command, Input, Outputcommand registry/dispatcher, tables, colour, quiet mode, capturable output
LoggingLoggerdaily/single file channels, levels, in-memory ring buffer, tail() for a log screen
SupportArr, Str, Json, Clock, helpers.phpapp(), 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:

SuffixEnginePackage
.phpbuilt-in plain PHP ($view is the factory)
.twigTwigtwig/twig
.blade.phpBladeilluminate/view
.latteLattelatte/latte
.mustacheMustachemustache/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:

  • EventsGecole\Framework\Events\Dispatcher, wildcard/interface listeners, and the application.booted, request.received and request.handled lifecycle events (event(), event_listen()).
  • Cache & filesystemapp()->cache() / cache() over an Array or File store (app.cache), and app()->files() for project I/O. The cache manager is extensible: CacheManager::addDriver() (or app.cache.drivers) registers any backend, and optional packages plug in without the core depending on them (e.g. gecole/cache-redis adds a redis driver).
  • Auth guardsAuthManager resolves named guards; token authenticates a Bearer token against personal_access_tokens (auth('token')), on top of the session web guard.
  • Route model bindingRoute::model('user', User::class) + the SubstituteBindings middleware hand the controller a model instead of an id.
  • Validation — database-aware unique and exists rules via a connection resolver. Custom rules are extensible without core changes: implement the Gecole\Framework\Validation\Rule contract (or pass a callable) and register it with Validator::extend('name', …) from a service provider (parameters after the colon are passed through). make:rule scaffolds one.
  • MiddlewareTrimStrings, HandleCors (with kernel-answered preflight), ThrottleRequests and Authenticate.
  • Console generatorsmake:controller|model|migration|command|provider|middleware|service|trait and app:key, scaffolding namespaced files under app.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):

PathMethodDefaultOverride
project rootbasePath()constructor argument
configconfigPath()config/app.paths.config
application classesappPath()src/App/app.paths.app
databasedatabasePath()database/app.paths.database
migrationsmigrationPath()database/migrations/app.paths.migrations
seedersseederPath()database/seeders/app.paths.seeders
publicpublicPath()public/app.paths.public
resourcesresourcePath()resources/app.paths.resources
viewsviewPath()resources/views/app.paths.views
storagestoragePath()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

KeyPurpose
app.name, app.env, app.debug, app.timezone, app.localeidentity of the application; debug decides how much an error response reveals
app.providersservice providers, register()d in order then boot()ed
app.middlewareglobal middleware, outermost first
app.middleware_groupsnamed middleware stacks (web, api, …) a route references by name
app.viewsdefault view engine, extra view paths, per-adapter options
app.apiprefix/middleware/name used when loading routes/api.php (starter convention)
app.sessioncookie name, lifetime, cookie options
app.loggingenabled, channel (daily/single), level, filename, requests
app.assetsmanifest, live, fallbacks
app.serverhost/port/workers for serve
app.paths, app.storage_pathdirectory overrides
database.default, database.connections.*, database.migrations_tablePDO connections and migration bookkeeping
auth.providerthe 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.debug the response carries the exception class, file, line and a trimmed trace; without it, a generic message and a line in storage/logs. An errors.error view 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.phpreturn ['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 :placeholder values and fall back to app.fallback_locale before returning the key unchanged.
  • trans_choice() pluralises Laravel-style | segments ({0}…|[1,Inf]…).
  • translator() / app()->translator() expose locale(), setLocale(), hasLocale(), locales(), direction() (rtl/ltr) and choice().
  • SetLocale middleware (in the web group) picks the locale from ?lang=, the session, or Accept-Language, persists it, and shares locale, dir and locales to the views. Views can use trans()/__() directly, and the plain-PHP/Twig/Blade/Latte/Mustache engines expose them too.
  • End users add a language by writing resources/lang/xx.php and adding xx to app.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.