Selima
Fullstack solo project — a Symfony 7.3 backend (admin back-office + REST API + public front) paired with an Expo / React Native mobile app.
Selima is a personal fullstack project I’m building end-to-end: a modular Symfony 7.3 backend that powers an admin back-office, a documented REST API and a public front-end, together with a cross-platform Expo / React Native mobile app that consumes the API.
Like the rest of the Own invention section, the product, the architecture and every line of code are mine — backend, mobile, infra and deploy.
Stack
| Layer | Tech |
|---|---|
| Backend | PHP 8.2+ / Symfony 7.3, custom bundles (Admin / Api / Front / MetierManager) |
| ORM / DB | Doctrine ORM, MySQL 8.0 |
| API | FOS REST Bundle, JMS Serializer, Nelmio API Doc (Stoplight UI) |
| Front (web) | Symfony AssetMapper + Stimulus + UX Turbo |
| Mobile | Expo / React Native, file-based routing via expo-router |
| Auth | Session-based form login (admin) + stateless token auth (API) |
| Infra | Docker / Docker Compose, Apache, phpMyAdmin |
Backend — Symfony 7.3
The app is split into four custom bundles under src/Selima/:
src/Selima/
AdminBundle/ → Back-office UI at /office
ApiBundle/ → REST API at /api
FrontBundle/ → Public front-end at /
MetierManagerBundle/ → Shared domain: entities, security, repositories, listeners
The root PSR-4 namespace is LCG\ mapped to src/. Routes are composed in config/routes.yaml with the prefixes /api/*, /office/* and /.
Entities
All entities extend an abstract EntityCore which adds dateAdd / dateUpd lifecycle callbacks:
EntityCore (abstract)
├── UserCoreEntity (abstract) — email, password, firstName, lastName, enabled, roles
│ ├── Employee — admin users (ROLE_ADMIN, ROLE_MODERATEUR, ROLE_SUPER_ADMIN)
│ ├── Customer — customer accounts
│ └── Professional — pro accounts
└── AddressCore — shared address fields
├── AddressCustomer
└── AddressProfessional
Table prefix
All tables are prefixed slm_ via a TablePrefixListener hooked into Doctrine’s loadClassMetadata event. The prefix comes from the DB_PREFIX parameter, so entity #[ORM\Table(name: ...)] declarations stay unprefixed and the listener adds the prefix at runtime.
Security
Three firewalls are configured:
-
selima_admin— session-based form login forEmployeeentities, guards/office(customAdminLoginFormAuthenticator). -
selima_api— stateless token authentication via a customTokenAuthenticator, guards/api. -
dev— bypasses security for the Symfony profiler.
Role hierarchy: ROLE_SUPER_ADMIN > ROLE_ADMIN (+ ROLE_MODERATEUR) > ROLE_USER.
API documentation
The REST API is documented with NelmioApiDoc. JMS Serializer @Serializer\Groups annotations control which fields are exposed per endpoint (e.g. USER_SINGLE).
- Stoplight UI:
/api/doc.html - OpenAPI JSON:
/api/doc.json
Running the backend
# 1. Environment
cp .env.example .env.local
# 2. Stack
docker compose up -d
# 3. Dependencies
docker exec -t symfony_app_selima_wapp composer install
# 4. Database
bash scripts/sf-cmd.sh doctrine:migrations:migrate
bash scripts/sf-cmd.sh doctrine:fixtures:load # optional
| Service | URL |
|---|---|
| App (HTTP) | http://localhost:8080 |
| App (HTTPS) | https://localhost:8443 |
| phpMyAdmin | http://localhost:8082 |
| MySQL | localhost:3306 |
| API docs (UI) | http://localhost:8080/api/doc.html |
| OpenAPI JSON | http://localhost:8080/api/doc.json |
A helper script wraps docker exec for any Symfony console command:
bash scripts/sf-cmd.sh cache:clear
bash scripts/sf-cmd.sh doctrine:migrations:diff
bash scripts/sf-cmd.sh doctrine:migrations:migrate
Tests
# Full suite
docker exec -t symfony_app_selima_wapp php bin/phpunit
# Single file
docker exec -t symfony_app_selima_wapp php bin/phpunit tests/path/to/FooTest.php
Mobile — Expo / React Native
The mobile client is bootstrapped with create-expo-app and uses file-based routing (expo-router) — every file under app/ becomes a route.
# 1. Install dependencies
npm install
# 2. Start the app
npx expo start
From the Expo CLI output you can open the app in:
- a development build
- an Android emulator
- an iOS simulator
- Expo Go, the sandbox client
Development happens inside the app/ directory. To reset back to a blank starter and move the example code into app-example/:
npm run reset-project
Environment
Copy .env.example to .env.local on the backend and configure:
-
DATABASE_URL— MySQL DSN -
DB_PREFIX— table prefix (defaultslm_) -
MAILER_*— SMTP settings
The Docker entrypoint copies .env.example to .env.prod.local inside the container automatically.
License
Proprietary — © LCG.