# Deploy a Composer PHP app on Yeeted

A small slug API in **PHP 8.3 managed by Composer**, with **no Dockerfile**:
Yeeted sees `composer.json`, installs the locked dependencies with
`composer install --no-dev`, and serves the folder with Apache, routing every
path that is not a file to `index.php`. The
[guestbook](guestbook-php.md) covers the other PHP path, with no
Composer manifest at all.

One real dependency, [cocur/slugify](https://github.com/cocur/slugify), does
the slugging, pinned by `composer.lock`. The app's own classes under `src/`
load through Composer's PSR-4 autoloader, so a deploy that serves a slug has
proved the install and the generated autoloader both worked.

- `GET /api/slug?text=...` returns `{"text": ..., "slug": ...}`; a missing or
  blank `text` returns 400.
- `GET /` is a form that calls the API; `GET /healthz` returns `ok`.

Tests: `php tests/test.php` (the slug rules and every route, no server
needed) · `./smoke.sh https://...` against a deployment.

## Run locally

Use PHP 8.3 or newer with mbstring, and Composer 2. From `php/slug-composer`:

```sh
composer install
php tests/test.php
php -S 127.0.0.1:8000 index.php
```

Open `http://localhost:8000`. The router argument is needed so API paths
reach `index.php` in the PHP development server. `composer.json` pins the
platform to PHP 8.3, the version the deployed image runs, so a newer local
PHP cannot lock a dependency the deployment cannot install.

## Deploy and verify

Follow the [deployment walkthrough](getting-started.md). Deploy this
folder, including `composer.json`, `composer.lock`, `src/`, `yeeted.yaml` and
the hidden `.htaccess` (some upload tools skip dotfiles; this one matters),
with no database and no Dockerfile. Do not upload `vendor/`; the install runs
on Yeeted. The manifest routes to port 80, where Apache listens.

```sh
sh smoke.sh "$PREVIEW_URL"
```

The script checks the health endpoint, the page, two slugs (one with
accents, which needs the dependency's transliteration rules), that a blank
request is refused with 400, and that `vendor/`, `src/` and `composer.lock`
answer 403.

## What to change before real use

The generated image serves this whole folder as the web root, with
`vendor/` installed inside it. The shipped `.htaccess` refuses `vendor/`,
`src/`, `tests/` and the Composer files, so only `index.php` and the routes it
handles answer; keep it, and extend it before adding any other file that must
not be public, such as configuration. For a larger app, ship your own
Dockerfile with a `public/` document root, or use a framework that Yeeted
serves from `public/` (Laravel, Symfony).

[All examples](index.md) · [Deploy an example](getting-started.md)
