PHPackages                             waffle-commons/skeleton - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Framework](/categories/framework)
4. /
5. waffle-commons/skeleton

ActiveProject[Framework](/categories/framework)

waffle-commons/skeleton
=======================

The official skeleton for Waffle Framework applications.

0.1.0-beta4(2w ago)110MITPHPPHP ^8.5

Since Jan 6Pushed 2w agoCompare

[ Source](https://github.com/waffle-commons/skeleton)[ Packagist](https://packagist.org/packages/waffle-commons/skeleton)[ RSS](/packages/waffle-commons-skeleton/feed)WikiDiscussions main Synced today

READMEChangelog (8)Dependencies (111)Versions (15)Used By (0)

[![Waffle Ecosystem Logo](https://github.com/waffle-commons/.github/raw/main/assets/logo.png)](https://github.com/waffle-commons/.github/blob/main/assets/logo.png)

🦁 Waffle Skeleton
=================

[](#-waffle-skeleton)

The official starting point for building robust, secure, and high-performance applications with the [Waffle Ecosystem](https://github.com/waffle-commons).

[![Minimum PHP Version](https://camo.githubusercontent.com/a3efcba14243fb59b4bc246efb4cca2aee6c5ae1a38ee305f736d4eabf2e96f1/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f7068702d382e352d626c75652e737667)](https://php.net/)[![Waffle Ecosystem](https://camo.githubusercontent.com/cbf98a6990845497840b316dc95b3213d9d468eb8cd08a048f8705b39ead9f4c/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f776166666c652d636f6d6d6f6e732d6f72616e6765)](https://github.com/waffle-commons)[![License](https://camo.githubusercontent.com/7013272bd27ece47364536a221edb554cd69683b68a46fc0ee96881174c4214c/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c6963656e73652d4d49542d626c75652e737667)](./LICENSE.md)

> **Release:** `0.1.0-beta4` | PHP 8.5+ · FrankenPHP worker mode

Welcome to the **Waffle Skeleton**, the official starting point for building robust, secure, and high-performance applications with the [Waffle Ecosystem](https://github.com/waffle-commons).

This skeleton is not just a folder structure; it is a **Production-Grade Boilerplate** pre-configured with:

- **FrankenPHP (Caddy)**: Modern application server with native HTTP/3 and Early Hints support.
- **Docker Multi-Stage**: Optimized images for Development (with Xdebug) and Production (Immutable, &lt;100MB).
- **Hardened Security Defaults**: Auto-generated secrets, fail-closed CORS (SEC-04), default-on SSRF protection on outbound calls (SEC-02), stateless HMAC CSRF (SEC-01), and the Universal Authentication Bridge (RFC-021).
- **Strict Standards**: PHP 8.5+, Typed Properties, and Read-Only classes.

🚀 Installation
--------------

[](#-installation)

### Prerequisites

[](#prerequisites)

- **Docker** &amp; **Docker Compose** (Required)
- **PHP 8.5+** &amp; **Composer** (Optional, for local commands)

### Create a New Project

[](#create-a-new-project)

Use Composer to create your project. This will automatically trigger the setup scripts to generate secure keys and initialize the directory structure.

```
composer create-project waffle-commons/skeleton my-app
cd my-app
```

> **Note:** If you don't have PHP installed locally, you can use the Docker setup immediately after cloning the repository manually.

🐳 Docker Environment
--------------------

[](#-docker-environment)

Waffle is Cloud-Native by design. We provide two distinct environments managed via a single Dockerfile.

### 1. Development Mode (dev)

[](#1-development-mode-dev)

Optimized for Developer Experience (DX).

- **Hot Reload:** Code is mounted via volumes. Changes are reflected instantly.
- **Debugging:** Xdebug is installed and configured.
- **Tooling:** Composer is available inside the container.

**Start the Dev Server:**

```
docker compose up --build -d
```

Your application is now available at: 👉 [**https://localhost**](https://localhost/) (Accept the self-signed certificate auto-generated by Caddy).

### 2. Production Mode (prod)

[](#2-production-mode-prod)

Optimized for Performance and Security.

- **Immutable:** No source code volumes. The code is baked into the image.
- **Fast:** Opcache validation is disabled, Preloading is enabled.
- **Secure:** Dev tools (Composer, Xdebug) are removed. Rootless execution.

**Test the Production Build locally:**

```
docker compose -f docker-compose.prod.yml up --build -d
```

📂 Directory Structure
---------------------

[](#-directory-structure)

A Waffle application follows a strict but simple structure:

```
.
├── config/                       # ⚙️ Configuration
│   ├── app.yaml                  # Main Waffle Configuration
│   └── preload.php               # Opcache Preloading Script
├── docker/                       # 🐳 Infrastructure as Code (Dockerfile, Caddyfile, PHP config)
├── migrations/                   # 🗃️ Versioned SQL migration scripts (bin/waffle db:migrate)
├── public/                       # 🌍 Web Entry Point (index.php)
├── scripts/                      # 🛠️ Composer Lifecycle Scripts
├── src/                          # 🧠 Your Application Logic (Namespace: App\)
│   ├── Controller/               # HTTP Entry points
│   ├── Factory/                  # Application Factory
│   │  ├── AppKernelFactory.php   # The Kernel Factory (Dependencies)
│   ├── Service/                  # Business Logic
│   └── Kernel.php                # The Application Core (Configuration & Boot)
├── tests/                        # 🧪 PHPUnit Test Suite
└── var/                          # 📦 Temporary files (Cache, Logs, Exports, etc) - Ignored by Git

```

🛠️ Configuration
----------------

[](#️-configuration)

### Environment Variables (`.env`)

[](#environment-variables-env)

Waffle ships a native `DotEnv` parser (no third-party dependency). When you run `create-project`, a `.env` file is automatically created from `.env.example` with a generated `APP_SECRET`.

VariableDescription`APP_ENV``dev` (debug enabled) or `prod` (optimized).`APP_DEBUG``true` displays detailed stack traces. `false` renders JSON errors.`APP_SECRET`32-byte Hex string used for cryptographic operations.`WAFFLE_CSRF_SECRET`32+ byte signing secret for stateless HMAC CSRF tokens (Beta-1 / SEC-01). Bound at boot by `AppKernelFactory::resolveCsrfSecret()`. In `prod`, a missing or short value aborts boot; non-prod falls back to a per-process random secret.`WAFFLE_AUTH_SECRET`32+ byte shared HMAC-SHA256 secret for the Universal Authentication Bridge (RFC-021). Signs `X-Wfl-Assert-User` identity assertions and validates the demo HS256 JWTs. In `prod`, a missing or short value aborts boot (fail-closed).`SERVER_NAME`The domain name used by Caddy (e.g., `example.com` or `localhost`).`DB_HOST` / `DB_PORT`Database host and port consumed by `waffle.database.*` (RFC-022).`DB_NAME`Database / schema name.`DB_USER` / `DB_PASSWORD`Database credentials. Override from your orchestrator in production.> **⚠ Precedence: OS env wins over `.env`.** `AppKernelFactory` merges your `.env` with the live process environment (Docker `environment:`, Kubernetes `env:`, shell exports, etc.) via `array_merge((new DotEnv($root))->load(), getenv())`. Because `array_merge` is rightmost-wins on string keys, **the OS value beats `.env`** on collision. If you edit `.env` and the change doesn't take effect, check whether the same variable is exported by your shell or `docker-compose.yml` — that export will silently override `.env`. This matches the Twelve-Factor convention. See [`documentation/how-to/configuration.md`](https://github.com/waffle-commons/documentation/blob/main/how-to/configuration.md) for the merge rules and the type-normalization foot-gun around `APP_DEBUG`/`DEBUG`.

### Framework Config (`config/app.yaml`)

[](#framework-config-configappyaml)

Waffle uses native YAML parsing (via PECL extension) for blazing-fast configuration loading.

```
# Main application configuration for the Waffle Framework
waffle:
  env: '%env(APP_ENV)%'
  debug: '%env(APP_DEBUG)%'
  # Host-header allowlist (anti-poisoning). REQUIRED in production.
  trusted_hosts:
    - localhost
  security:
    level: 10
    csrf:
      # SEC-01: stateless HMAC CSRF secret. Prod refuses to boot without a 32+ byte value.
      secret: '%env(WAFFLE_CSRF_SECRET)%'
    # SEC-04: fail-closed CORS. Empty ⇒ every cross-origin request is rejected.
    # Add exact origins (scheme://host[:port]); never '*' with credentials.
    cors:
      allowed_origins: []
    # SEC-02: the outbound HTTP client resolves → validates → pins every host,
    # refusing private/loopback/reserved IPs. allowed_hosts whitelists trusted
    # internal backends (exact host or CIDR) — keep it tight.
    ssrf:
      allowed_hosts: []
  # Universal Authentication Bridge (RFC-021). The secret aborts boot in prod if absent.
  auth:
    secret: '%env(WAFFLE_AUTH_SECRET)%'
    tenant: 'skeleton'
    jwt:
      issuer: 'https://waffle-dev.local'
      audience: 'waffle-skeleton'
  paths:
    controllers: 'src/Controller'
    services: 'src/Service'
  # Database & migrations (RFC-022). Credentials resolved from DB_* env vars.
  database:
    driver: 'mysql'
    host: '%env(DB_HOST)%'
    port: '%env(DB_PORT)%'
    database: '%env(DB_NAME)%'
    username: '%env(DB_USER)%'
    password: '%env(DB_PASSWORD)%'
    charset: 'utf8mb4'
    migrations_path: 'migrations'
```

> The shipped `config/app.yaml` also wires `log` (PSR-3 channel) and `cache` (PSR-16 adapter) — see the file for the full, commented set.

### Security defaults (Beta-4)

[](#security-defaults-beta-4)

The skeleton ships the canonical Beta-4 middleware pipeline out of the box:

```
ErrorHandler → TrustedHost → CORS → AnonymousSession → Authentication → Routing → CSRF → Security → SecureHeaders → Dispatcher

```

This means every controller action you write is, by default, subject to:

- **Fail-closed ABAC** — an action without `#[Voter]` returns HTTP `403`. Tag explicitly public actions with `#[\Waffle\Commons\Contracts\Security\Attribute\PublicAccess]`.
- **Stateless HMAC CSRF on mutating routes (SEC-01)** — opt actions in with `#[RequiresCsrfToken]`. Tokens are HMAC-bound to the per-browser `WAFFLE_SID` cookie issued by `AnonymousSessionMiddleware`, which rotates the SID on privilege change.
- **Fail-closed CORS (SEC-04)** — cross-origin requests are rejected unless their origin is listed in `waffle.security.cors.allowed_origins`; wildcard-with-credentials is refused at construction.
- **Universal Authentication (RFC-021)** — the `auth` bridge verifies inbound credentials (JWT / OAuth2-OIDC / HMAC assertion / API key / Basic) fail-closed and publishes the identity for ABAC.

The outbound HTTP client is hardened too: **default-on SSRF protection (SEC-02)** resolves → validates → pins every target host and refuses private/reserved addresses, with `waffle.security.ssrf.allowed_hosts` whitelisting trusted internal backends.

See the framework docs at [`waffle-commons/documentation`](https://github.com/waffle-commons/documentation) for the full design rationale.

🗃️ Database &amp; Migrations
----------------------------

[](#️-database--migrations)

Waffle ships a lightweight, forward-only SQL migration runner (RFC-022, via `waffle-commons/data`). Database access is configured under `waffle.database` in `config/app.yaml` (credentials from the `DB_*` env vars), and the connection pool + runner are wired in `src/Factory/AppKernelFactory.php` and registered as `db:migrate` in `bin/waffle`.

Write versioned SQL scripts in `migrations/`, named `Version_.sql`, then apply the pending ones from the project root:

```
php bin/waffle db:migrate
```

Each migration runs in its own transaction and is recorded in a `waffle_migrations` table, so re-runs skip already-applied scripts. The skeleton ships a sample `migrations/Version2026053101_CreateUsersTable.sql` to get you started. See the [Database Migrations how-to](https://github.com/waffle-commons/documentation/blob/main/how-to/database-migrations.md) for the full workflow and the MySQL transactional-DDL caveat.

👩‍💻 Usage Example
-----------------

[](#‍-usage-example)

### 1. Create a Service

[](#1-create-a-service)

Create `src/Service/Greeter.php`:

```
