waffle-commons/skeleton

The official skeleton for Waffle Framework applications.

Maintainers

Package info

github.com/waffle-commons/skeleton

Type:project

pkg:composer/waffle-commons/skeleton

Statistics

Installs: 7

Dependents: 0

Suggesters: 0

Stars: 1

Open Issues: 0

0.1.0-beta2.1 2026-05-30 19:19 UTC

README

Waffle Ecosystem Logo

๐Ÿฆ Waffle Skeleton

The official starting point for building robust, secure, and high-performance applications with the Waffle Ecosystem.

Minimum PHP Version Waffle Ecosystem License

Welcome to the Waffle Skeleton, the official starting point for building robust, secure, and high-performance applications with the Waffle Ecosystem.

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, <100MB).

  • Zero-Config Security: Automatic secret generation and hardened defaults.

  • Strict Standards: PHP 8.5+, Typed Properties, and Read-Only classes.

๐Ÿš€ Installation

Prerequisites

  • Docker & Docker Compose (Required)

  • PHP 8.5+ & Composer (Optional, for local commands)

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

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

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 (Accept the self-signed certificate auto-generated by Caddy).

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

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)
โ”œโ”€โ”€ 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

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.

Variable Description
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.
SERVER_NAME The domain name used by Caddy (e.g., example.com or localhost).

โš  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 for the merge rules and the type-normalization foot-gun around APP_DEBUG/DEBUG.

Framework Config (config/app.yaml)

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

# Main application configuration for the Waffle Framework
waffle:
  # App environment (dev, prod, test). Set via server environment variable.
  env: '%env(APP_ENV)%'
  # Debug mode. Set to false in production for security.
  debug: '%env(APP_DEBUG)%'
  security:
    level: 10
    csrf:
      # Beta-1 / SEC-01: signing secret for the stateless HMAC CSRF subsystem.
      # Resolved via getenv(WAFFLE_CSRF_SECRET). Production refuses to boot
      # without a 32+ byte value.
      secret: '%env(WAFFLE_CSRF_SECRET)%'
  paths:
    controllers: 'src/Controller'
    services: 'src/Service'

Beta-1 security defaults

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

ErrorHandler โ†’ TrustedHost โ†’ AnonymousSession โ†’ 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 โ€” opt actions in with #[RequiresCsrfToken]. Tokens are HMAC-bound to the per-browser WAFFLE_SID cookie issued by AnonymousSessionMiddleware.

See the framework docs at waffle-commons/documentation for the full design rationale.

๐Ÿ‘ฉโ€๐Ÿ’ป Usage Example

1. Create a Service

Create src/Service/Greeter.php:

<?php

namespace App\Service;

readonly class Greeter
{
    public function sayHello(string $name): string
    {
        return "Welcome to Waffle, {$name}!";
    }
}

2. Create a Controller

Create src/Controller/HelloController.php. Waffle uses Attributes for routing.

<?php

namespace App\Controller;

use App\Service\Greeter;
use Psr\Http\Message\ResponseInterface;
use Waffle\Commons\Routing\Attribute\Argument;
use Waffle\Commons\Routing\Attribute\Route;
use Waffle\Core\BaseController;

class HelloController extends BaseController
{
    // Services are automatically injected (Autowiring)
    public function __construct(
        private Greeter $greeter
    ) {}

    #[Route(path: '/greet/{name}', method: 'GET')]
    public function index(string $name): ResponseInterface
    {
        $message = $this->greeter->sayHello($name);

        return $this->jsonResponse(data: [
            'message' => $message,
            'status' => 'success'
        ]);
    }
}

Go to https://localhost/greet/Developer. You should see your JSON response!

๐Ÿงช Running Tests

The skeleton comes with PHPUnit pre-configured.

# Run tests inside the Docker container
vendor/bin/phpunit

๐Ÿค Contributing

We welcome contributions! Please see CONTRIBUTING.md for details.

๐Ÿ“„ License

This project is licensed under the MIT License - see the LICENSE.md file for details.