A production-ready Symfony-based REST API application demonstrating modern PHP development practices, comprehensive quality assurance, and automated deployment workflows.
Some links navigate to other files in this repository for better readability.
- Overview
- Installation
- Testing
- Deployment
- GitHub Actions
- Architecture & Design Decisions
- Project Structure
- API Documentation
- Troubleshooting
This project showcases a professional Symfony API backend with:
- Modern PHP Stack: Symfony 7.3, PHP 8.4, PostgreSQL 17
- API-First Design: API Platform with OpenAPI/Swagger documentation
- Quality Assurance: PHPStan Level 8, PHPUnit tests, PHP CS Fixer
- CI/CD Pipeline: Automated testing, Docker image builds, deployment to Render
- Production-Ready: Docker containerization, health checks, structured logging
- Infrastructure as Code: Render deployment configuration in
render.yaml
Modern, enterprise-grade PHP framework. Latest versions since this is a fresh start.
Choice: Automatic REST API generation with Hypermedia support
Rationale: Dramatically reduces boilerplate code for CRUD operations. Auto-generates OpenAPI documentation, provides built-in validation, pagination, and filtering.
Trade-off: Less control over API response structure (JSON-LD format), but gains in development speed and standardization are significant.
Choice: Latest PostgreSQL version Rationale: I learn something new, robust ACID compliance, advanced indexing, JSON support, and excellent performance. PostgreSQL 17 provides improved query optimization and better logical replication. Trade-off: Requires PostgreSQL-specific knowledge compared to MySQL, but superior feature set justifies the choice.
┌─────────────────────────────────┐
│ API Platform (Presentation) │ ← REST endpoints, OpenAPI docs
├─────────────────────────────────┤
│ Controllers (Application) │ ← Business logic orchestration
├─────────────────────────────────┤
│ Entities & Repositories │ ← Domain models, data access
├─────────────────────────────────┤
│ Doctrine ORM (Persistence) │ ← Database abstraction
└─────────────────────────────────┘
Choice: Single image with nginx + PHP-FPM managed by Supervisor Rationale: Simpler deployment, single image to manage, reduced orchestration complexity. Ready for Container Platform Render.com. Trade-off: Less microservices-oriented, but for a monolithic API, single container deployment is more practical. Alternative Considered: Separate nginx and PHP containers → Rejected due to deployment complexity for this scale. Like this it works out of the box.
Choice: Separate dev
and prod
stages in Dockerfile
Rationale: Development needs all dependencies (PHPStan, PHPUnit, etc.) while production should be lean. Multi-stage builds allow single Dockerfile for both environments.
Trade-off: Slightly more complex Dockerfile, but eliminates need for separate dev/prod Dockerfiles and ensures consistency.
Implementation: compose.override.yaml
automatically uses dev
target locally; CI/CD and production use prod
target.
Choice: Custom base image (ghcr.io/wysselbie/apiplatform-base:php8.4-1.0.0
)
Rationale: Faster builds (pre-installed PHP extensions), consistent environment, reduced CI build time.
Trade-off: Additional maintenance overhead for base image, but significant CI speed improvements. And adjustments does not happen that often.
Choice: GHCR over Docker Hub or private registries
Rationale: Native GitHub integration, free for public repositories, same authentication as source code.
Choice: Automatic transaction rollback per test
Rationale: Fast test execution (no database reset), isolated tests, no side effects between tests.
Trade-off: Cannot test transaction-specific behavior, but gains in speed and simplicity are worth it.
Choice: PCOV instead of Xdebug
Rationale: 2-5x faster coverage collection, purpose-built for coverage analysis.
Trade-off: No debugging capabilities, but CI only needs coverage, not debugging.
Choice: Strictest static analysis level Rationale: Catches bugs before runtime, enforces type safety, improves code quality. Trade-off: More initial effort to satisfy type requirements, but code quality is important.
Choice: CI pipeline checks for un-migrated entity changes
Rationale: Prevents production schema mismatches, enforces migration discipline.
Implementation: doctrine:migrations:diff
in CI fails if differences detected.
Choice: Dedicated test environment (APP_ENV=test
)
Rationale: Best practice. Isolates test data, prevents development data pollution.
Choice: GitOps-style deployment to Render.com by updating render.yaml
Rationale: Explicit deployment control, infrastructure version-controlled, audit trail in git history. Render supports GitOps-style deployments for prebuilt images while others don't. Also Render.com can be used to spin up a database and other services. All with internal network access for security and better performance.
Trade-off: Manual step required (not fully automated), but provides human approval gate before production changes. Lock-in to Render.com, but all used services are for free at the moment.
api-demo/
├── .github/
│ └── workflows/ # GitHub Actions CI/CD
├── config/
│ ├── packages/ # Symfony bundle configurations
│ ├── routes/ # Routing configuration
│ └── services.yaml # Service container config
├── docker/ # Docker configuration
├── src/
│ ├── ApiResource/ # API Platform resources
│ ├── Controller/
│ ├── Entity/ # Doctrine entities
│ ├── Repository/
│ └── Kernel.php # Application kernel
├── tests/
│ ├── Functional/ # Integration/API tests
│ ├── Unit/ # Unit tests
│ ├── bootstrap.php # Test environment setup
│ ├── console-application.php # PHPStan helper
│ └── object-manager.php # PHPStan Doctrine helper
├── var/
│ ├── cache/ # Symfony cache
│ ├── log/ # Application logs
│ └── coverage/ # Test coverage reports
├── vendor/ # Composer dependencies
├── Makefile # Development commands
└── render.yaml # Render.com IaC config
OpenAPI Documentation:
- Interactive API docs:
http://localhost:8080/api
- OpenAPI JSON spec:
http://localhost:8080/api/docs.json
Command | Category | Description |
---|---|---|
make help |
General | Show all available commands |
make install |
Setup | Install Composer dependencies |
Testing & Quality | ||
make test |
Quality | Run PHPUnit tests (no coverage) |
make test-coverage |
Quality | Run tests with HTML coverage |
make coverage-report |
Quality | Open coverage report in browser |
make phpstan |
Quality | Run PHPStan static analysis |
make phpstan-baseline |
Quality | Generate PHPStan baseline |
make cs-check |
Quality | Check code style compliance |
make cs-fix |
Quality | Auto-fix code style issues |
make quality |
Quality | Run PHPStan + PHPUnit |
make full-check |
Quality | Complete project validation |
make security-check |
Security | Run Composer security audit |
make validate-schema |
Database | Validate Doctrine schema |
Database | ||
make db-create |
Database | Create database |
make db-migrate |
Database | Run database migrations |
make db-setup |
Database | Create and migrate database |
make db-reset |
Database | Drop, recreate, migrate database |
make db-test-setup |
Database | Setup isolated test database |
make db-test-reset |
Database | Reset test database with fresh data |
Development | ||
make serve |
Development | Start PHP development server |
make serve-bg |
Development | Start dev server in background |
make api-docs |
Development | Show API documentation URLs |
Maintenance | ||
make clean |
Maintenance | Clear cache and temporary files |
make cache-clear |
Maintenance | Clear Symfony cache |
make cache-warmup |
Maintenance | Warm up Symfony cache |
make requirements-check |
Maintenance | Check Symfony requirements |
For experienced developers, common commands have short aliases:
Shorthand | Full Command | Description |
---|---|---|
make cc |
cache-clear |
Clear Symfony cache |
make cw |
cache-warmup |
Warm up Symfony cache |
make t |
test |
Run PHPUnit tests |
make tc |
test-coverage |
Run tests with coverage |
make ps |
phpstan |
Run PHPStan analysis |
make cf |
cs-fix |
Auto-fix code style |
make q |
quality |
Run all quality checks |
make m |
db-migrate |
Run database migrations |
make s |
serve |
Start development server |
Docker Issues:
- Port Already in Use: Change ports in
compose.yaml
if 8080/5432 are occupied - Build Failures: Run
docker compose down
thendocker compose up --build
- Database Connection: Ensure database service is healthy:
docker compose logs database
- Permission Issues: Reset containers with deleting volumes:
docker compose down -v && docker compose up --build
Local Development Issues:
- Database Connection: Check
DATABASE_URL
in.env.local
- Cache Issues: Run
make cache-clear
- Permission Issues: Ensure
var/
directory is writable - Missing Dependencies: Run
make install
- Code Coverage Error: Install PCOV (
pecl install pcov
) or runmake test
instead ofmake test-coverage
- Install of PCOV not possible (macOS; missing pcre2.h): Link missing library
ln -s /opt/homebrew/opt/pcre2/include/pcre2.h /opt/homebrew/opt/php@8.4/include/php/ext/pcre/
CI/CD Issues:
- Image Build Failures: Check Docker build logs in GitHub Actions
- Schema Drift: Run
php bin/console doctrine:migrations:diff
to generate migrations
- Clone the repository
- Create a feature branch (
git checkout -b feature/amazing-feature
) - Make your changes
- Run quality checks:
make full-check
- Commit your changes (
git commit -m 'Add amazing feature'
) - Push to the branch (
git push origin feature/amazing-feature
) - Open a Pull Request
- PSR-12: PHP code style standard
- Symfony Conventions: Follow Symfony best practices
- PHPStan Level 8: Strict type checking
- 100% Test Coverage: For critical business logic
- Meaningful Commits: Use conventional commit messages
- Code follows PSR-12 and Symfony standards (
make cs-check
) - All tests pass (
make test
) - PHPStan analysis clean (
make phpstan
) - No security vulnerabilities (
make security-check
) - Documentation updated (README, docblocks)
- Migration files included (if schema changes)
- Changelog updated (if applicable)
This project is available under the Forklift Certified License, version 0.69.420, by Aria Salvatrice.