ppi/skeleton-app
A starter skeleton application for the PPi PHP framework, providing the base structure, bootstrap wiring, and configuration needed to quickly create and launch a new PPi project.
This is a project template, not a library — you don't require it into an existing app, you scaffold from it. Treat composer create-project (or a manual clone + composer install) as step one, and expect to own the resulting codebase outright.
composer.json first. It tells you the PHP constraint, the ppi/framework version it pins, and which "extra goodies" (templating, ORM, mailer, logging) are actually wired in versus merely suggested. Everything else in the skeleton exists to satisfy this file.require block before you install anything modern; if you're on PHP 8 you're in for dependency surgery, not a create-project and go.composer.lock immediately. As of 2.1.2 the skeleton no longer ships a lock file, so composer install resolves fresh every time against floating constraints. Do one install, verify it boots, commit the resulting lock, and treat that lock as part of your project — see the gotchas for why this matters more here than usual.public/. public/index.php is the front controller — the document root must be public, never the repo root. For a quick look: php -S 127.0.0.1:8000 -t public. Make sure the cache and log directories are writable before you debug a blank page.public/index.php → app/config/ → app/modules/. That chain is the whole mental model of the framework. index.php selects the environment and boots the application; config layers are merged; modules register their services and routes.vendor/ppi/ if you need to see how HTTP messages are actually wired.)Module-per-feature. The module is the unit of composition: each has a Module.php with lifecycle hooks (init/boot-style) where you register services, routes, and event listeners. Organize your own code the same way — one module per bounded feature, not one giant "App" module. Modules are also the cheapest thing to extract later if you need to reuse code elsewhere.
Service container as the seam. Define services and dependencies in the module, resolve them through the container, and inject into controllers. Resist pulling the container into domain/business classes — that's the one decision that makes or breaks maintainability in a container-driven framework, and it's easy to get wrong when you're learning from the skeleton's demo code.
Layered config. Expect module-level defaults overridden by application-level config, merged by environment. Keep environment-specific values (DB credentials, debug flags, mail transport) out of module config and in your app-level env config so modules stay portable.
Routing stays in the module. Register routes alongside the services they serve. That keeps a module self-contained and deletable.
Third-party libraries via Composer, wrapped in a module. Don't sprinkle vendor calls through controllers; register one service per dependency so swapping or mocking is a one-line config change. Pin those dependencies explicitly in your lock file — with no lock shipped upstream, a floating ^ constraint is the thing most likely to break your build six months from now.
Deployment workflow: composer install --no-dev -o on the target from a committed lock file, clear the compiled cache, docroot at public/, writable cache/log dirs, debug off. Do that sequence in a script — the cache step is the one people forget and then chase ghosts over.
composer install floats against whatever satisfies the constraints today. For a framework this old, that's a real risk of pulling a newer transitive dependency that breaks a 2016 codebase. Generate a lock on day one, commit it, and use composer install (not update) everywhere thereafter.vendor/ppi/ source is the reference. When a lifecycle hook or config key doesn't behave as the README implies, read the framework class — it's small.composer dump-autoload. Add the namespace to composer.json (PSR-0/PSR-4 as the skeleton demonstrates) and regenerate. A module that loads in one environment and not another is almost always an autoloader dump that didn't happen.--ignore-platform-reqs smell. If you find yourself bypassing platform checks to install, stop and reconsider the dependency set rather than papering over it — you're trading a five-minute fix for a debugging session later. Same applies to forcing resolution because the lockless install picked something incompatible.How can I help you explore Laravel packages today?