Directory Structure

A freshly created Preflow project has this layout:

myapp/
├── app/
│   ├── Components/        Co-located PHP + template UI components
│   │   └── ExampleCard/
│   │       ├── ExampleCard.php
│   │       └── ExampleCard.twig
│   ├── Controllers/       Attribute-routed controllers
│   │   └── HealthController.php
│   ├── Models/            Data models with #[Entity] attributes
│   │   ├── Post.php
│   │   └── User.php
│   ├── Providers/         Service providers
│   │   └── AppServiceProvider.php
│   ├── Seeds/             Demo data seeders
│   └── pages/             File-based routes (Twig templates)
│       ├── _layout.twig
│       ├── index.twig
│       ├── about.twig
│       └── blog/
│           ├── index.twig
│           └── [slug].twig
├── config/
│   ├── app.php
│   ├── auth.php
│   ├── data.php
│   ├── i18n.php
│   ├── middleware.php
│   └── providers.php
├── lang/                  Translation files
│   ├── en/
│   └── de/
├── migrations/            Database schema migrations
├── public/                Web root
│   ├── index.php
│   └── .htaccess
├── storage/
│   ├── cache/             Compiled templates and route cache
│   ├── data/              SQLite database and JSON data files
│   └── logs/              Application logs
├── tests/                 PHPUnit tests
├── .env                   Environment variables
├── composer.json
└── preflow               CLI entry point

app/Components/

Each component lives in its own directory containing a PHP class and a matching template. The class handles state and actions; the template handles presentation. CSS and JS can be co-located using {% apply css %} and {% apply js %} blocks inside the template.

app/Components/Counter/
├── Counter.php       # Component class (resolveState, actions)
└── Counter.twig      # Template (uses public properties as variables)

Use components in any template with {{ component('Counter', { count: 0 }) }}.

app/pages/

Templates here map directly to URLs. The directory structure defines the route hierarchy:

File URL
index.twig /
about.twig /about
blog/index.twig /blog
blog/[slug].twig /blog/{slug} (dynamic)
docs/[...path].twig /docs/* (catch-all)
_layout.twig Not a route (underscore prefix)

Files prefixed with _ are excluded from routing. _layout.twig serves as the base layout that page templates extend.

app/Controllers/

Controllers handle API endpoints and form submissions using PHP attribute routing:

#[Route('/api/posts')]
final class PostController
{
    #[Get('/')]
    public function index(): ResponseInterface { /* ... */ }

    #[Post('/')]
    public function create(): ResponseInterface { /* ... */ }
}

Controller routes resolve to action mode; page routes resolve to component mode. Both work side by side.

config/

Plain PHP files returning arrays. Loaded at boot time and accessible via dot notation (app.name, data.default). See the Configuration guide for details on each file.

public/

The web root directory. Only index.php and static assets live here. The entry point is three lines:

<?php

require __DIR__ . '/../vendor/autoload.php';

Preflow\Core\Application::run(__DIR__ . '/..');

Point your web server's document root here. Apache uses the included .htaccess; Nginx needs a try_files directive.

storage/

Runtime data that should not be committed to version control:

Directory Contents
storage/cache/ Compiled Twig templates, route cache
storage/data/ SQLite database file, JSON data files
storage/logs/ Application log files

Clear the cache with php preflow cache:clear.