Skip to content

Application classes

Greg Bowler edited this page May 18, 2026 · 3 revisions

Page logic handles one request. Application classes handle the application's behaviour in a reusable way.

That distinction is worth keeping clear early. If a page needs to load a user, validate some input, save a record, or calculate a result, the page file should usually pass that work into an application class rather than own the whole process itself.

Rule of thumb: Page logic should do as little as possible, deferring work to application classes as soon as possible.

Organising classes of an application

WebEngine is designed to hand control over to your own application code as early as possible. The framework deals with the request, routing, page setup, and dependency wiring, then your page logic should pass the real work into ordinary PHP classes wherever possible.

That design is deliberate. It means the important behaviour of the project can live in classes that are not tightly bound to one page, one URL, or even one framework. Those classes can be tested in isolation, reused across different pages, and refactored with the normal benefits of object-oriented code.

In a standard project, those classes are autoloaded by Composer from the application's main namespace and PSR-4 root. By default that means the App\ namespace maps to the class/ directory, though both are configurable.

An application class can take many forms. It might be:

  • a small data transfer object with almost no behaviour
  • a service class that performs one business action
  • a repository that loads and builds richer objects from the database
  • a value object that gives a small concept a clear type and API

The important part is not the label - what's important is that the page hands off responsibility quickly, so the application's behaviour lives in classes with focused responsibilities.

Even the simplest systems benefit from this. For example, a small greeter class can keep the page entry point almost trivial:

class/GreetingService.php

namespace App;

class GreetingService {
	public function forName(string $name):string {
		return "Hello, $name";
	}
}

Assuming the GreetingService is constructed in the service container, then the page logic only has to orchestrate:

use GT\DomTemplate\Binder;
use GT\Input\Input;
use App\GreetingService;

function go(Binder $binder, Input $input, GreetingService $greetingService):void {
	$name = $input->getString("name") ?: "guest";
	$binder->bindKeyValue("greeting", $greetingService->forName($name));
}

That is the basic WebEngine shape: the page understands the web request, the application class understands the behaviour, and the view receives the final data.

Common types of classes

Common classes in a WebEngine application include:

  • services that coordinate useful work
  • use-case classes that perform one application action
  • domain entities that represent application data with behaviour
  • repositories or data access helpers
  • value objects for small typed concepts

Not every project needs all of these, but they are common shapes that help the codebase grow cleanly.

How Page Logic should use them

Page logic should stay thin. Let the page handler read the request, call the right class, and bind the final output data into the document.

That keeps the boundary clear. The page knows about the web request. The application class knows about the business rule. The view knows about presentation.

Keeping code framework-agnostic

As far as practical, application classes should not be tightly coupled to WebEngine. They should not need to know about the page lifecycle just to do their job.

That makes them easier to test and easier to reuse. It also keeps the framework at the edge of the application rather than at the centre of every class.


Next we'll learn about the client-side build system.

Clone this wiki locally