What this actually is. xsanisty/slim-starter (SlimBoard) is not a library you composer require into a Laravel app — it is a skeleton application: a Slim 2.x project wired up with HMVC-style modules, a Twig view layer, an ORM, config files and a DI container. The last release was May 2014, so treat it as a reference architecture and a legacy maintenance target, not a dependency. If your goal is "borrow the module idea for Laravel," skip to Implementation Patterns. If your goal is "run this thing," keep reading.
Minimal boot:
composer create-project xsanisty/slim-starter myapp
cd myapp
composer install
Because the lockfile-era dependencies are pre-PHP-7, pin the interpreter explicitly if Composer refuses to resolve:
"config": { "platform": { "php": "5.6.40" } }
Then:
public/ (or the repo root, depending on the skeleton) with mod_rewrite / try_files enabled — Slim 2 routing dies silently on a 404-ing rewrite.app/config/database.php (host, name, user, pass) and app/config/app.php (debug, name, salt).app/storage (cache, logs, sessions) is writable by the web user.Where to look first, in order:
public/index.php — the entire bootstrap: autoloader, config load, container bindings, module registration, middleware, $app->run().app/config/ — plain PHP arrays, merged into $app->config(). No .env, no config:cache.modules/ — one directory per module, each with Controllers/, Models/, Views/, and a routes.php.Controller class — this is where $this->app, $this->render() and the view/data plumbing live.First use case. Build a throwaway Pages module: a controller with index/show, a Twig template extending the default layout, and two routes in modules/Pages/routes.php. That single exercise touches every layer you'll interact with day to day — router, container, ORM, view — in about 40 lines.
Module anatomy. Each module is a self-contained slice of MVC:
modules/Blog/
├── Controllers/PostController.php
├── Models/Post.php
├── Views/index.twig, show.twig
└── routes.php
Routes are registered by the bootstrapper, which iterates the module directories. Consequence: adding a module requires composer dump-autoload if the module namespace is classmap-discovered, and a page refresh otherwise.
Routing — Slim 2 style, no Laravel router. Methods are get/post/put/delete, not resourceful verbs, and the callable can be a Class:method string:
<?php
// modules/Blog/routes.php
$app->group('/blog', function () use ($app) {
$app->get('/', 'Blog\Controllers\PostController:index')->name('blog.index');
$app->get('/:slug', 'Blog\Controllers\PostController:show')->name('blog.show');
});
Named routes let you generate URLs in Twig with {{ urlFor('blog.show', {slug: post.slug}) }} — the closest analogue to route().
Controllers — thin, like you'd write in Laravel.
<?php
namespace Blog\Controllers;
class PostController extends \SlimBoard\Controller
{
public function index()
{
$posts = \Blog\Models\Post::orderBy('created_at', 'desc')->get();
$this->render('blog/index.twig', ['posts' => $posts]);
}
}
render() resolves the template against the module's Views/ path. If the ORM is Eloquent-based, Post::where(...)->first() is identical to Laravel — models and query builder carry over almost verbatim. If a module uses a Paris/Idiorm-style ORM instead, replace ->get() with ->find_many() and forget relationships.
Views — Twig, not Blade. Layout inheritance and blocks are the mental translation you'll do all day:
{% extends 'layouts/default.twig' %}
{% block content %}
{% for post in posts %}
<h2><a href="{{ urlFor('blog.show', {slug: post.slug}) }}">{{ post.title }}</a></h2>
{% endfor %}
{% endblock %}
Escape is {{ }} (auto), {% autoescape false %} for raw. There's no @include('partial') — use {% include 'partial.twig' %}.
Container bindings. Slim 2's $app->container is a Pimple-ish set. Bind services once at bootstrap, resolve in controllers — this is your Service Provider analogue:
$app->container->singleton('mailer', function () use ($app) {
return new \Blog\Services\Mailer($app->config('mail'));
});
Middleware. Auth, CSRF and logging are wrapped around the whole app rather than attached to route groups, so cross-cutting concerns are global. For per-route protection, guard inside the controller or inside the group callback — there's no ->middleware() chain.
Borrowing the pattern for Laravel. If you're assessing this to bring structure back to a Laravel codebase, the transferable idea is domain-module cohesion, expressible as a package with loadRoutesFrom(), loadViewsFrom() and mergeConfigFrom() in a service provider — a direct one-to-one replacement for the modules/*/routes.php + Views/ convention.
[]-short-array conventions in places, and PHP 7's removal of mcrypt. Slim\Extras\Middleware\CsrfGuard and StrongHash depend on mcrypt — on PHP 7.2+ they will fatal. Port them to random_bytes() / hash_equals() before anything else..env. Config is committed PHP arrays. Don't put credentials in them; add a local.php override loaded last in the bootstrap and gitignore it.var_dump country. Set 'debug' => true, then register Slim's Slim\Log and a Whoops handler manually — there's no error page out of the box. In Twig, {{ dump() }} requires enabling the Twig debug extension in the view bootstrap; do that on day one.composer dump-autoload -o is your first move on any unexplained 500./blog and /blog/ as distinct. Define the more specific route first (/:slug after /archive) or you'll shadow it.Slim::getInstance(); never call it from models or helpers. Inject $app — it keeps the eventual port to a real container manageable.routes.php → routes/web.php; Controllers/render() → Controller/view(); $app->container->singleton → provider bind/singleton; Twig → Blade (or keep Twig via twigbridge); Phinx → Schema::create. Controllers and models port in an afternoon; the module loader and config layer are the only genuinely bespoke code, and both are worth rewriting rather than porting.How can I help you explore Laravel packages today?