Why we built Zana, and what it does differently
Zana is our open-source PHP framework for business applications. Here are the problems it solves: modules with checked boundaries, a container that fails at startup instead of in production, and documentation that is tested.

In Swahili, zana means “tool”. That is the job we gave the framework: to be a tool that helps a team build business software. It shouldn't be a world you have to learn before you can work. Zana is our open-source PHP framework, released under the MIT licence. We build client projects on it, and the site you are reading runs on it.
This article explains the problems Zana was built to solve and the choices that follow from them.
The problem: business software grows sideways
Most business applications start small: a few screens, a database and a contact form. Then they grow. Invoicing arrives, then approvals, reporting, a mobile API and a back office. After two years, the code that sends an invoice knows about the code that approves a purchase order. That code knows about the reporting tables, and nobody can change one part without fear.
Frameworks are rarely the cause, but they rarely help either. They organise code by technical layer: all controllers here, all models there. The business boundaries that matter, like invoicing, patients or inventory, are left to discipline. Discipline does not survive deadlines.
Zana turns that around.
Modules first
A Zana application is a list of modules. Each one is a folder that owns a business capability completely: its domain, its use cases, its storage and its screens.
// config/modules.php
return [
Modules\Patients\PatientsModule::class,
Modules\Bmi\BmiModule::class,
];
A module declares what it depends on:
final class BmiModule extends Module
{
public function name(): string { return 'Bmi'; }
public function dependsOn(): array { return [PatientsModule::class]; }
public function register(ServiceCollectionInterface $services): void
{
$services->scoped(MeasurementRepository::class, PdoMeasurementRepository::class);
}
}
Modules are sorted so dependencies come first. An unknown dependency or a cycle stops the application at startup. Boundaries are also checked, not just recommended. One test applies Zana's dependency rules to every module, including the ones you add next year:
public function testEveryModuleFollowsTheDependencyRules(): void
{
$this->assertModuleArchitecture(ModuleRules::for(__DIR__ . '/../modules'));
}
The rules are simple. The domain depends only on Zana's DDD building blocks. Presentation never reaches into the domain or the database. One module may use another only through its public Contracts. Break a rule, and the test names the class and the import.
Fail at startup, not in production
The second idea is about when mistakes surface.
In many PHP applications, a wiring mistake shows up only when a page needs the broken service. That might be a missing dependency, or a long-lived service holding on to something that should live for one request. If that page is the monthly report, you find out at the end of the month.
Zana's container checks the whole application when it is built:
$services = new ServiceCollection();
$services->singleton(LoggerInterface::class, FileLogger::class);
$services->scoped(UnitOfWork::class);
$services->transient(CalculateBmiHandler::class);
$container = $services->build(); // validates everything, then freezes the collection
Every constructor must be satisfiable. A singleton that depends on a scoped service, even through other services, is rejected before the first request. Each error names the module the registration came from, so you know where to look.
The same idea applies to deployment. In production, the application refuses to start if debug mode is on, if debug is on for a public address, or if a secret is still a placeholder. You can add your own checks. A bad deployment fails loudly in the terminal and never quietly in front of customers.
Fast because it is compiled
Strict does not have to mean slow. In production, Zana compiles the container into plain PHP and caches the configuration. The compiled container resolves 100 services 3.3 times faster than at runtime. An HTTP round trip costs about 70 µs in a booted application. Most of your time goes to your own code and your database, which is where it should go.
Built for business logic
Zana includes the building blocks business applications keep reinventing: aggregates and entities, immutable value objects, typed identities, domain events, and a Result type for expected failures. It also has a mediator that validates commands before they reach a handler, and units of work that commit a change as a whole.
final readonly class PatientId extends Identity {}
final class Patient extends AggregateRoot
{
public static function register(PatientId $id, string $name): self
{
$patient = new self($id);
$patient->recordEvent(new PatientRegistered($name));
return $patient;
}
}
None of this is required. A small website can ignore it completely. But when a project grows into real business rules, the tools are already there, and they follow the same conventions as the rest of the framework.
Standards, and only what you use
Zana uses PSR interfaces throughout: the container, HTTP messages, logging, events and caching. Your code and the libraries you bring stay portable. Zana is also split into packages, so you install only what you use. A JSON API does not ship templates, and a website does not ship a message bus. Every package is tested with PHPUnit and analysed with PHPStan at level 8.
Documentation you can trust
Out-of-date documentation is worse than none, because you believe it. Every code sample in Zana's documentation is a runnable file, checked by tests against its documented output. When behaviour changes, the docs either change too or the build fails.
Who Zana is for
Zana suits teams building applications that will live for years and keep growing: internal tools, portals, platforms, back offices and APIs. It is just as comfortable running a multilingual website with a back office, like this one.
To try it:
composer create-project mj-zana/skeleton-web my-site
cd my-site
composer serve
Then open http://localhost:8000. The getting started guide takes it from there.
If you would like help adopting Zana, such as training, an architecture review or a project built with it, talk to us. We build it, and we use it every day.
