Inside a framework-free application
The skeleton we start from
The web server points at a public folder that holds one PHP file and your assets. Everything else, including configuration, templates and classes, sits above the web root where a browser cannot request it. That single entry file loads the Composer autoloader, reads settings from an environment file, starts the session and hands the request to a router. Controllers stay thin: read input, call a service, return a response. Business rules live in plain classes with no knowledge of HTTP, which is what makes them testable with PHPUnit.
Micro-framework or hand-rolled
We rarely write a router or a template engine ourselves. Slim, or a few Symfony components wired together, gives you routing, request and response objects and middleware in a small, well-maintained package. We go fully dependency-free only when the brief demands it, for example a single-file tool that must be dropped onto servers we do not control. Either way the rule is the same: every package in composer.json has to earn its place, because each one is something to update later.
Security is where plain PHP projects fail
A framework gives you CSRF protection, escaping and query binding without asking. Without one, they exist only if someone builds them. So we build them first, before any feature. Every POST, PUT and DELETE route checks a per-session token. Every template value passes through an escaping helper unless it is explicitly marked as safe HTML. Every query uses bound parameters, including the ones on admin screens that only staff will see. File uploads are checked by content type and size, renamed, and stored where they cannot be executed. Then we test it the unfriendly way, by submitting forms without tokens, pasting script tags into every field and tampering with IDs in URLs.
When no framework is the right call
Choose this route when the tool is small and well defined, when it has to run on shared hosting, when it should keep working for years with little attention, or when it must sit inside another PHP site without clashing with it. Fewer moving parts means fewer forced upgrades and a codebase one developer can hold in their head.
When it is the wrong call
The moment a brief includes queues, complex permissions, a public API, multi-step workflows and a growing team, plain PHP stops being the economical choice. You would end up paying us to rebuild what a framework already ships. We would rather tell you that on the first call. For work in that range, see PHP web application development, where a framework is usually the starting point.