Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 5 additions & 17 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,21 +1,9 @@
# PivotPHP Skeleton Environment
# Copied to .env by `composer create-project`. Real environment variables take precedence.
APP_NAME="PivotPHP Skeleton API"
APP_ENV=development
APP_DEBUG=true
APP_URL=http://localhost:8000

# OpenAPI/Swagger
OPENAPI_ENABLED=true
SWAGGER_UI_ENABLED=true
# Debug responses expose exception messages and stack traces. Never enable in production.
APP_DEBUG=false

# Performance
CACHE_ROUTES=false
OPTIMIZE_RESPONSES=true

# Security
CORS_ENABLED=true
RATE_LIMITING=false
SECURE_HEADERS=false

# Logging
LOG_LEVEL=debug
# Comma-separated list of origins allowed by CORS (e.g. http://localhost:5173). Empty = disabled.
CORS_ALLOWED_ORIGINS=
61 changes: 61 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
name: CI

on:
push:
branches: [ main ]
pull_request:
branches: [ main ]

permissions:
contents: read

jobs:
tests:
runs-on: ubuntu-latest
name: PHP ${{ matrix.php }} · ${{ matrix.deps }}
strategy:
fail-fast: false
matrix:
include:
- php: '8.1'
deps: 'lowest'
- php: '8.3'
deps: 'highest'
- php: '8.4'
deps: 'highest'

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
extensions: mbstring, json
coverage: none

- name: Validate composer.json
run: composer validate --strict

- name: Install dependencies
run: |
if [ "${{ matrix.deps }}" = "lowest" ]; then
composer update --prefer-lowest --prefer-stable --no-progress --no-interaction
else
composer update --prefer-stable --no-progress --no-interaction
fi

- name: Audit dependencies
run: composer audit

- name: Run PHPUnit
run: vendor/bin/phpunit

- name: Smoke test (built-in server)
run: |
php -S 127.0.0.1:8000 -t public > /dev/null 2>&1 &
sleep 1
curl -fsS http://127.0.0.1:8000/health
curl -fsS -o /dev/null -w '%{http_code}\n' -X POST http://127.0.0.1:8000/api/users \
-H 'Content-Type: application/json' -d '{"name":"Ana","email":"ana@example.com"}' | grep -q 201
9 changes: 9 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,11 @@
/vendor/
composer.lock
.env
/coverage/
/.phpunit.cache/
.phpunit.result.cache
.idea/
.vscode/
.DS_Store
CLAUDE.md
.claude/
29 changes: 29 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,35 @@ All notable changes to the PivotPHP Skeleton project will be documented in this
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [2.0.0] - 2026-10-10

Template for the PivotPHP 4 ecosystem (SPEC-089).

### Changed

- Requires **`pivotphp/core` ^4.0.1** (brings `pivotphp/http`, `pivotphp/core-routing` ^2.2 and
`pivotphp/security`); `composer create-project` installs again (SPEC-098).
- Controllers use `ExpressRequest`/`ExpressResponse` (`$req->input()`, `$req->param()`) and return
proper status codes: `201` + `Location`, `204` without body, `404`, `422` with field errors.
- New `bootstrap/app.php` builds the application for both `public/index.php` and the tests.
- `config/app.php` is loaded (application created with its base path) and reads the environment;
`.env` is copied from `.env.example` on `create-project`.
- CORS via `pivotphp/security` `CorsMiddleware`, restricted to `CORS_ALLOWED_ORIGINS`.

### Fixed

- `POST`/`PUT /api/users` returned 500 (`body()` treated as an array) (SPEC-082).
- `config/` was never loaded, and debug defaulted to **on** when `APP_DEBUG` was unset, exposing stack
traces (SPEC-084). Debug is now off unless `APP_DEBUG=true`.
- Tests depended on `storage/` (created only by `create-project`) and on a hard-coded framework version
(SPEC-083).

### Removed

- `App\Middleware\CorsMiddleware` (wildcard origin, preflight answered with 200 and a JSON body).
- Unused configuration keys (`openapi`, `performance`, `security`, `logging`), the `storage/` layout and
hard-coded framework versions and performance figures.

## [1.1.1] - 2026-10-09

### Fixed
Expand Down
225 changes: 51 additions & 174 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,205 +1,82 @@
# PivotPHP Skeleton

**The fastest way to start building APIs with PivotPHP v2.2.0**
Project template for [PivotPHP](https://github.com/PivotPHP/pivotphp-core) 4 — an Express.js-style PHP
API on PSR-7/PSR-15.

[![PivotPHP](https://img.shields.io/badge/PivotPHP-v2.2.0-blue.svg)](https://github.com/PivotPHP/pivotphp-core)
[![PHP Version](https://img.shields.io/badge/PHP-^8.1-777BB4.svg)](https://php.net)
[![License](https://img.shields.io/badge/License-MIT-green.svg)](LICENSE)

> 🚀 **"Route Syntax & DX Edition"** - PivotPHP v2.2.0 Edition

## ✨ What's Included

This skeleton provides everything you need to start building modern PHP APIs:

- 🎯 **PivotPHP v2.2.0** - Latest framework with educational focus
- 📚 **Optional OpenAPI/Swagger** - Interactive API documentation at `/swagger`, opt-in (a few lines in `public/index.php` — see [Enabling API Documentation](#enabling-api-documentation-optional) below)
- 🚀 **Express.js Syntax** - Familiar, intuitive routing patterns
- 🏗️ **MVC Structure** - Controllers, middleware, and clean organization
- ✅ **PHPUnit Testing** - Ready-to-use testing setup
- 🔧 **Development Tools** - Built-in server, debugging, and more

## 🚀 Quick Start
## Quick start

```bash
# Create a new project
composer create-project pivotphp/skeleton my-api

# Enter project directory and start server
cd my-api && composer serve
cd my-api
composer serve # http://localhost:8000
```

Your API is now running at **http://localhost:8000**! 🎉

## 📖 Available Endpoints
`create-project` copies `.env.example` to `.env`. Requires PHP 8.1+ with `ext-mbstring`.

Once running, you can access:
## Endpoints

| Endpoint | Description |
|----------|-------------|
| `GET /` | Welcome message with API info |
| `GET /health` | Health check endpoint |
| `GET /api/status` | API status and metadata |
| `GET /api/users` | List all users (CRUD example) |
| `POST /api/users` | Create new user |
| `GET /api/users/{id}` | Get user by ID |
| `PUT /api/users/{id}` | Update user |
| `DELETE /api/users/{id}` | Delete user |
| Method | Path | Description |
|---|---|---|
| GET | `/` | Welcome message |
| GET | `/health` | Health check |
| GET | `/api/status` | Name, environment and framework version (from `config/app.php`) |
| GET | `/api/users` | List users |
| POST | `/api/users` | Create a user (`name`, `email`; JSON or form) — `201` + `Location`, `422` on invalid data |
| GET | `/api/users/:id` | Show a user — `404` if missing |
| PUT | `/api/users/:id` | Update `name`/`email` |
| DELETE | `/api/users/:id` | Delete — `204` |

`GET /swagger` and `GET /docs` (interactive API documentation and its OpenAPI 3.0 JSON spec) are **not** available out of the box — they require enabling `ApiDocumentationMiddleware` first. See [Enabling API Documentation](#enabling-api-documentation-optional) below.
The example users live in memory, so every request starts from the same three users. Replace the array
in `UserController` with your persistence layer.

## 🏗️ Project Structure
## Structure

```
my-api/
├── app/
│ ├── Controllers/ # API controllers
│ │ ├── ApiController.php
│ │ └── UserController.php
│ └── Middleware/ # Custom middleware
│ └── CorsMiddleware.php
├── config/
│ └── app.php # Application configuration
├── public/
│ └── index.php # Application entry point
├── routes/
│ └── api.php # API route definitions
├── storage/
│ └── logs/ # Application logs
├── tests/
│ └── ApiTest.php # Example tests
├── .env # Environment configuration
├── composer.json # Dependencies and scripts
└── README.md # This file
app/Controllers/ ApiController (container-injected), UserController (CRUD example)
bootstrap/app.php builds the application: config, middlewares, controllers, routes
config/app.php app.* configuration, read from the environment
public/index.php HTTP entry point
routes/api.php routes
tests/ApiTest.php tests through the real application ($app->handle())
```

## 🛠️ Development Commands

```bash
# Start development server
composer serve

# Run tests
composer test
## Configuration

# Run tests with coverage report
composer test:coverage
```

## 📚 Enabling API Documentation (optional)
`.env` is loaded before `config/`; real environment variables take precedence.

The `@route`, `@summary`, `@tags` and `@response` PHPDoc comments you'll see above
each route in `routes/api.php` are for human readers only — `pivotphp/core` does
not parse them, and this skeleton does not register anything that would. By
default, **`/swagger` and `/docs` are not available**.
| Variable | Default | Purpose |
|---|---|---|
| `APP_NAME` | `PivotPHP Skeleton API` | Application name |
| `APP_ENV` | `production` | Environment name |
| `APP_DEBUG` | `false` | Error responses with messages and stack traces — **never enable in production** |
| `CORS_ALLOWED_ORIGINS` | *(empty)* | Comma-separated origins allowed by CORS; empty disables CORS headers |

To enable them, register `PivotPHP\Core\Middleware\Http\ApiDocumentationMiddleware`
in `public/index.php`, before `$app->run()`:
## Writing routes

```php
use PivotPHP\Core\Middleware\Http\ApiDocumentationMiddleware;

$app->use(new ApiDocumentationMiddleware([
'docs_path' => '/docs', // JSON OpenAPI 3.0 spec (note: not /openapi.json)
'swagger_path' => '/swagger', // Swagger UI
]));
```

Once registered, the middleware builds the OpenAPI spec from the routes actually
registered on the `Router` at runtime — the PHPDoc blocks above are not read by it,
they are purely documentation for developers browsing `routes/api.php`.
// routes/api.php
$app->get('/hello/:name', fn ($req, $res) => $res->json(['hello' => $req->param('name')]));

After adding this, visit **http://localhost:8000/swagger** to see interactive API docs.

## 🎯 Express.js-Style Routing

Write routes that feel familiar:

```php
// Simple route
$app->get('/', function($req, $res) {
return $res->json(['message' => 'Hello World!']);
$app->post('/items', function ($req, $res) {
$name = $req->input('name'); // JSON or form body
return $res->status(201)->json(['name' => $name]);
});

// Route parameters
$app->get('/users/{id}', function($req, $res) {
$id = $req->param('id');
return $res->json(['user_id' => $id]);
});

// Array callables (work from PHP 8.1+; the legacy 'Controller@method' string
// syntax is what breaks under PHP 8.4+, not array callables themselves)
$app->post('/users', [UserController::class, 'store']);

// Middleware — CorsMiddleware ships as an example class in
// app/Middleware/CorsMiddleware.php but is NOT registered anywhere by
// default. Register it yourself in public/index.php before $app->run():
$app->use(new CorsMiddleware());
```

## 🔧 Configuration

Edit `config/app.php` to customize your application:

```php
return [
'name' => 'My Awesome API',
'openapi' => [
'title' => 'My API Documentation',
'version' => '2.0.0'
],
'performance' => [
'cache_routes' => true, // Enable in production
'optimize_responses' => true
]
];
```
Handlers receive `PivotPHP\Http\ExpressRequest`/`ExpressResponse` and must return the response.
Controllers are array callables (`[UserController::class, 'index']`); register controllers that need
constructor dependencies in the container (see `bootstrap/app.php`).

## 🧪 Testing
Security middlewares (CORS, security headers, JWT, CSRF, rate limiting) come from
[`pivotphp/security`](https://github.com/PivotPHP/pivotphp-security); this template enables CORS.

Write tests in the `tests/` directory:
## Commands

```php
class MyApiTest extends TestCase
{
public function testWelcomeEndpoint(): void
{
// Your API tests here
$this->assertTrue(true);
}
}
```bash
composer serve # built-in server on localhost:8000 (docroot: public/)
composer test # PHPUnit
```

## 📈 Performance

Historical PivotPHP v1.2.0 benchmark figures (Docker-validated at the time), not
revalidated against this skeleton or the current `pivotphp/core` release line —
treat as indicative, not a guarantee:

- **2,122 req/sec** peak HTTP performance
- **3.6M ops/sec** OpenAPI generation
- **Docker validated** benchmarks (as of the v1.2.0 release)

## 🚀 Next Steps

1. **Customize your API** - Edit routes in `routes/api.php`
2. **Add controllers** - Create new controllers in `app/Controllers/`
3. **Build middleware** - Add custom middleware in `app/Middleware/`
4. **Write tests** - Add tests in `tests/`
5. **Deploy** - Use your preferred deployment method

## 📖 Learn More

- [PivotPHP Documentation](https://pivotphp.github.io/website/)
- [GitHub Repository](https://github.com/PivotPHP/pivotphp-core)
- [Performance Benchmarks](https://pivotphp.github.io/website/docs/benchmarks/)

## 📄 License

The PivotPHP Skeleton is open-sourced software licensed under the [MIT license](LICENSE).

---

**Built with ❤️ by the PivotPHP Team**
## License

*"Making PHP development joyful again"*
MIT
Loading
Loading