Skip to content

WavestormSoftware/wavephp-starter

Repository files navigation

MyApp

A real, opinionated starter for WavePHP — a small, secure, fast PHP 8.4+ framework.

This isn't the upstream tide new skeleton. It's a full demo application that exercises every feature of the templating engine and gives you a real layout, partials, error pages, auth scaffold, and CRUD to start from.

Quick start

# 1. Install dependencies
composer install

# 2. Create your .wave.env (the APP_KEY is required for CSRF + signed URLs)
cp .env.example .wave.env
php -r "echo 'APP_KEY=' . bin2hex(random_bytes(32)) . PHP_EOL;" >> .wave.env

# 3. Initialize the database
php vendor/bin/tide db:migrate
php vendor/bin/tide db:seed

# 4. Start the dev server
php -S 127.0.0.1:8000 -t public
# or:
php vendor/bin/tide serve

Open http://127.0.0.1:8000.

What's in the box

Area What you get
Layouts layouts/base.wave (grandparent), layouts/app.wave (standard page) — both chain via {% extends %}
Partials partials/header, partials/footer, partials/flash, partials/forms/csrf, partials/forms/field, partials/pagination, partials/empty
Error pages errors/404, errors/403, errors/500, errors/maintenance — rendered by app/Middleware/NotFoundHandler
Home Marketing-style welcome page with a feature grid, recent posts, filters, and url()
Auth Session-based registration, login, logout, remember-me cookie, CSRF tokens, password hashing, rate-limit-ready
Posts Full CRUD resource with create/edit/delete, validation, pagination, slug generation
ORM App\Models\User (with posts() hasMany), App\Models\Post (with user() belongsTo, scopes, casts)
Events App\Events\UserRegisteredApp\Listeners\SendWelcomeEmailApp\Jobs\SendWelcomeEmailJob
Mail App\Mail\WelcomeMail extending Wave\Mail\WaveMailable with a .wave HTML body
Queue Wave\Queue\Attributes\OnQueue + #[Retries], dispatched after registration
WebSockets App\Channels\ChatChannel registered in routes/channels.php
Components App\Components\CounterComponent (server-driven UI demo) with resources/views/components/counter.wave
Console tide inspire (sample command) registered in routes/cli.php
OpenAPI / GraphQL Auto-registered at /api/docs, /api/docs.json, /graphql, /graphql/playground
Tests Wave\Testing\WaveTestCase (in-memory SQLite), assertOk, assertSee, assertDatabaseHas etc.
Tooling phpunit.xml, phpstan.neon, .php-cs-fixer.dist.php, .editorconfig

Project structure

my-app/
├── app/                       your code
│   ├── Channels/              WebSocket channels
│   ├── Components/            WaveComponent classes
│   ├── Console/Commands/      tide CLI commands
│   ├── Controllers/           HTTP controllers
│   ├── Events/                Wave\Events\WaveEvent subclasses
│   ├── Jobs/                  Wave\Queue\WaveJob subclasses
│   ├── Listener/              Wave\Events event listeners
│   ├── Mail/                  Wave\Mail\WaveMailable subclasses
│   ├── Middleware/            PSR-15 middleware
│   ├── Models/                Wave\Database\Model subclasses
│   └── Support/               Plain PHP helpers
├── bootstrap/                 framework bootstrap (env, helpers, session)
├── config/                    framework config (app, db, cache, auth, …)
├── database/
│   ├── factories/             Wave\Seed\Factory subclasses
│   ├── migrations/            Wave\Database\Migration files
│   └── Seeders/               Wave\Seed\WaveSeeder subclasses
├── public/                    web root (point your server at this)
│   └── index.php              single front controller
├── resources/
│   └── views/                 .wave templates (layouts, partials, errors, …)
├── routes/
│   ├── api.php                /api/* routes
│   ├── channels.php           WebSocket channel handlers
│   ├── cli.php                console command registrations
│   └── web.php                HTTP routes
├── storage/                   writable: cache, logs, sessions, uploads
├── tests/                     PHPUnit 11 tests
│   ├── Unit/
│   ├── Feature/
│   └── TestCase.php
├── .env.example
├── .php-cs-fixer.dist.php
├── .editorconfig
├── composer.json
├── phpstan.neon
├── phpunit.xml
└── README.md

Common commands

# Dev server
php -S 127.0.0.1:8000 -t public
vendor/bin/tide serve

# Database
vendor/bin/tide db:migrate
vendor/bin/tide db:seed
vendor/bin/tide db:status

# Scaffolding
vendor/bin/tide make:controller FooController
vendor/bin/tide make:model Post
vendor/bin/tide make:migration create_posts_table

# Testing
vendor/bin/tide test                # or: composer test

# Quality
composer cs-fix                     # php-cs-fixer fix
composer stan                       # phpstan analyse

Templating cheatsheet

This boilerplate uses every feature of the Wave\Template\TemplateEngine. The home page (resources/views/home.wave) is a working example for most of them:

{# Layouts — extend either a top-level layout or a parent view #}
{% extends 'layouts/app' %}

{# Override a named block from the parent #}
{% block title %}Welcome{% endblock %}
{% block content %}
    <h1>Hello</h1>
{% endblock %}

{# Variables: escaped by default #}
{{ $user.name }}

{# Dot notation: $user['name'] #}
{{ $user.display_name }}

{# Raw output (use only for trusted HTML) #}
{{ $rawHtml | raw }}

{# Filters: upper, lower, trim, length, truncate, join, format, ... #}
{{ $post.title | upper }}
{{ $post.body | truncate(120) }}
{{ $post.published_at | format('Y-m-d') }}

{# Control structures #}
{% if $user %}
    <p>Hello, {{ $user.name }}</p>
{% else %}
    <p>Hello, guest</p>
{% endif %}

{% foreach $posts as $post %}
    <li>{{ $post.title }}</li>
{% endforeach %}

{# Layout slots — render only if the child fills them #}
{% slot 'actions' %}
    <a class="btn" href="...">Click me</a>
{% endslot %}

{# Partials #}
{% include 'partials/forms/csrf' %}
{% include 'partials/forms/field' with { name: 'email', type: 'email', value: $old.email ?? '', errors: $errors } %}

{# CSRF helpers (PHP-side) #}
{{ csrf_field() | raw }}
{{ csrf_meta() | raw }}

{# URL helper for named routes #}
{{ url('home') }}
{{ url('posts.show', {id: 42}) }}

Adding a new page

  1. Create the controller:

    vendor/bin/tide make:controller PagesController
  2. Add a method:

    #[Route('GET', '/about')]
    public function about(): Response
    {
        return $this->render('about', ['team' => $team]);
    }
  3. Create the view (resources/views/about.wave):

    {% extends 'layouts/app' %}
    {% block title %}About{% endblock %}
    {% block heading %}<h1>About</h1>{% endblock %}
    {% block body %}
        <p>...</p>
    {% endblock %}

That's it. Routes discovered by #[Route] are wired automatically by Wave::bootstrap().

Security notes

  • CSRF tokens are emitted automatically in every form via partials/forms/csrf and validated in every POST handler. Tokens are stored in the session.
  • Passwords use bcrypt via Wave\Security\Security::hashPassword().
  • Sessions are configured by config/session.php (file driver by default; switch to database to use the bundled sessions table).
  • app/Middleware/MethodOverride.php rewrites POST requests whose body contains _method=PUT|PATCH|DELETE — required for HTML form-based DELETE.
  • SQL injection is prevented automatically: every query through the ORM is parameterized.

License

MIT.

About

Starter template for WavePHP applications, scaffolded by `tide new`.

Resources

License

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages