MVC tutorial · Chapter 02

Understand the
PHPAML structure.

Learn where every responsibility belongs and why the application stays lightweight, readable, and separate from its private engine.

01Request
02Controller
03Model
04View
05Response
Core idea

You work mainly in src, routes, phpaml.json, and .env. AML manages runtime. This boundary protects the engine and keeps the root understandable.

Before exploring folders

Architecture is a map of responsibilities.

A good structure is not designed to multiply folders. It quickly answers one question: where should this code live so another developer understands its role without opening it? PHPAML therefore separates the HTTP entry point, routes, coordination, business rules, presentation, and generated infrastructure.

  • A URL is declared in routes/.
  • An HTTP action is coordinated by a controller.
  • A business rule belongs in a model or service.
  • Presentation belongs in src/views.

Project map

One place for every responsibility.

The official PHPAML template structure.

phpaml — zsh
my-project/
├── src/
│   ├── controllers/HomeController.php
│   ├── models/Home.php
│   └── views/{pages,components}/
├── routes/webapp.php
├── public/index.php       # web entry point
├── phpaml.json            # editable settings
├── .env                   # local secrets
└── runtime/               # AML-managed engine
    ├── storage/database.sqlite
    └── database/migrations/
02.1

Understand MVC

MVC divides the application into clear responsibilities. The model represents data and business rules. The controller receives the request and coordinates the work. The view produces the HTML sent to the browser.

MModelData and rules
CControllerCoordination
VViewHTML
02.2

Controllers and models

Controllers live in src/controllers. Models live in src/models and concentrate data access and business logic.

phpaml — zsh
namespace App\Controllers;

final class HomeController
{
    public function index(): array
    {
        return ['message' => 'Hello PHPAML'];
    }
}
02.3

Views and components

Views live in src/views. Pages compose the screen; components isolate reusable elements. A view receives data prepared by the controller.

phpaml — zsh
<?php require __DIR__ . '/partials/header.php'; ?>

<h1><?= htmlspecialchars($message) ?></h1>

<?php require __DIR__ . '/partials/footer.php'; ?>
02.4

Routes and configuration

routes/webapp.php connects URLs to controllers. phpaml.json contains editable project settings; secrets stay in .env. Generated internal configuration belongs to the runtime.

phpaml — zsh
use App\Controllers\HomeController;

Route::get('/', [HomeController::class, 'index']);
phpaml — zsh
{
  "name": "my-project",
  "type": "webapp",
  "language": "en"
}
02.5

Database and migrations

SQLite points to runtime/storage/database.sqlite by default. Generated migrations remain in runtime/database/migrations so the structure is reproducible and versioned.

runtime/database/migrations/Generated migrationsruntime/storage/database.sqliteGenerated local database
02.6

Public surface

public/index.php receives web requests. Documents requiring a direct URL, such as favicon, robots.txt, or sitemap.xml, may stay in public. Code and secrets never belong there.

public/index.phpfavicon.svgrobots.txtsitemap.xml
02.7

The AML-managed runtime

runtime contains the framework, autoloader, Composer, storage, and caches. phpaml.json identifies the project and expected versions. AML generates and updates the runtime through aml install.

You editsrc/ · routes/ · phpaml.json · .env
AML managesruntime/

Explore every responsibility

Understand before you start coding.

Read the architecture as the journey of information rather than a folder list to memorize. These situations explain the choices you will make in a real project.

02.1

Understand MVC

Imagine an online library. Without structure, one file may read the URL, query SQLite, calculate reading rights, and write HTML. It works initially, but every change can later break everything else.

MVC prevents that mixture. The controller understands the intent: display book 42. The model retrieves the book and applies its rules. The view presents its title, author, and reading button.

MVC does not require three files for every page. A static page may need no model; an API may return JSON without a view. Always identify the responsibility being performed.

Understand, don't memorize

Changing a title color affects the view. Blocking expired accounts belongs to the model or a business service.

02.2

Controllers and models

A controller is a conductor, not the entire orchestra. It receives HTTP parameters, validates their shape, calls the business layer, and selects a response.

For GET /books/42, it extracts 42, asks the model for the book, returns 404 when absent, and passes the result to the view. It should not contain long SQL queries or HTML.

The model represents data and behavior, such as guaranteeing a non-empty title or progress between 0 and 100. Those rules then work from pages, APIs, commands, and tests.

Understand, don't memorize

If a rule must remain true when the interface changes, it probably does not belong in the controller.

02.3

Views and components

A view receives an already prepared result. It does not decide which books a reader may access; it decides how to display them clearly and accessibly.

Classic PHPAML composes navigation, cards, messages, and footers. AML View expresses the same idea declaratively with pages, components, layouts, VStack, Text, and Button.

Extract a component when a pattern has meaning or repeats. Splitting every line into another file makes reading harder.

Understand, don't memorize

A view may format a date, but it must not open the database or contain secrets. Escape user-provided output.

02.4

Routes and configuration

A route is the readable entrance to a feature. It associates an HTTP method and path with an action: GET reads, POST creates, PATCH updates, and DELETE removes.

routes/webapp.php should remain a map rather than becoming another controller.

phpaml.json stores shareable project choices. .env stores machine-specific and secret values, allowing the manifest to be versioned safely.

Understand, don't memorize

Shared by the team: phpaml.json. Different per machine or secret: .env.

02.5

Database and migrations

The database preserves state beyond one request. SQLite is an excellent starting point: one file, no separate server, and familiar SQL.

A migration describes a reproducible structural change. Manual database edits cannot be reproduced by teammates or production.

A later PHPAML Data chapter covers entities, typed sets, queries, relations, transactions, SQL, and MongoDB. For now, distinguish business data from generated runtime storage.

Understand, don't memorize

Do not usually commit database.sqlite. Keep migrations because they describe the structure's history.

02.6

Public surface

The browser may directly request anything inside public. That makes this directory useful and security-sensitive.

public/index.php is the entry point. favicon, robots.txt, and sitemap.xml may stay public because they require direct URLs.

Never expose .env, phpaml.json, src, or runtime. Production's document root must target public rather than the complete project.

Understand, don't memorize

Before adding a file, ask whether anyone should be allowed to download it through a browser.

02.7

The AML-managed runtime

runtime is the rebuildable part. AML installs the framework, autoloading, private dependencies, caches, generated configuration, and some storage there.

This boundary keeps the root readable and tells AML what it may update. A manual runtime fix disappears after reinstallation.

Fix application code in src, routes, phpaml.json, or .env. Fix framework defects in its repository and publish a version. runtime remains the reproducible output of aml install.

Understand, don't memorize

The project describes the application; runtime provides its private execution environment.

Final exercise

Place each responsibility.

Locate the route, controller, model, page, components, phpaml.json, .env, and SQLite. Explain each role without opening runtime.

Chapter complete when…✓ MVC✓ routes✓ runtime

Follow a request

From /movies to the HTML response.

  1. public/index.phpreceives the request and boots the application.
  2. routes/webapp.phpmaps GET /movies to a controller method.
  3. src/controllersvalidates input and requests the required data.
  4. src/modelsapplies business rules and communicates with persistence.
  5. src/viewsturns prepared data into an interface.

This flow is intentionally predictable. When a screen shows a wrong value, trace the path backward: view, controller, model, then data source. When a URL does not respond, begin with the route rather than the CSS.

Solution and pitfalls

Decide by responsibility, not habit.

Form validationsrc/controllers
Price calculationsrc/models
GET /about routeroutes/webapp.php
Database credential.env
Public project namephpaml.json
Direct faviconpublic/
Common mistakes

Do not put an SQL query in a view, a password in phpaml.json, or a business rule in public/index.php. Do not edit runtime to work around a problem: fix the project source, then let AML rebuild the infrastructure.

Mini quiz: where does middleware belong?

In src/middleware when it is application code. The corresponding generated configuration remains internal to the runtime.

Chapter 01Chapter 03 · Coming soon 🔒