Project consists of vanilla PHP api backend and React.js frontend
โ ๏ธ PHP API is responsible for all core logic, where key principles, design patterns, and techniques have been applied.
React is used only for the displaying and interacting with data, following a component-based principles.Feel free to refactor either part as needed. Good luck!
- ๐งฉ Functionality
- ๐ป How to Run
- โ๏ธ How Does It Work
- ๐ Programming Principles
- ๐๏ธ Design Patterns
- ๐ง Refactoring Techniques
- ๐ Line of code
- ๐ฅ Demonstration
The core idea of this project revolves around a shipment management system with tracking used by both regular users and administrators.
- ๐น Users can interact with their shipments (both sent and received), manage their profile, and create support tickets in case of issues.
- ๐น Admins oversee all aspects of the system: managing users, shipments, post offices, and user support tickets.
- Login โ Authenticate using email and password.
- Registration โ Register a new account with email and password.
- Profile โ View and update personal information.
- Shipments โ View all shipments sent by the user and create new shipments.
- Receiving โ Track shipments received from other users.
- Tracking โ Monitor the status, current location, departure and destination, and dates for all user-related shipments.
- Support โ Submit support tickets for any issues and review the status of existing tickets (open or closed).
- Profile โ Same as regular user, with the ability to update personal information.
- Tracking โ Access and manage all usersโ shipments, with controls to cancel or update shipment status and details.
- Users โ View all registered users and remove accounts if necessary.
- Post Offices โ Manage post office records, including creating new entries or updating existing ones.
- Support Tickets โ Oversee all support requests, including replying, closing, or reopening tickets as needed.
All the data is saved in mysql database managed by phpMyAdmin
Follow the steps below to get the project running locally using Docker.
- โ
Make sure Docker and Docker Compose are installed on your machine.
๐ Docker Installation Guide (macOS, Windows, Linux)
git clone https://github.com/merelythesame/post-tracking-system.git
cd post-tracking-systemIn the root of the project directory, run:
docker compose up -d --build
โ ๏ธ It may take a few minutes to download and build all the required images. Please be patient.
Check if all containers are up and running:
docker compose psYou should see four containers with status Up:
php-backendreact-frontendmysql-dbpma(phpMyAdmin)
Access phpMyAdmin at:
๐ http://localhost:8080
The project is configured to auto-import the database on launch. If for some reason the tables are missing:
- Navigate to your database in phpMyAdmin. Database user is root and password is root
- Click the "Import" tab in the top menu.
- Select the file:
./db/project.sql - Click "Import" to load the database schema and data.
You should now see all required tables.
- ๐ Frontend (React UI): http://localhost:5137
- ๐ phpMyAdmin (DB UI): http://localhost:8080
- ๐ Backend (PHP API): http://localhost:8000
๐งช You can test API routes using Postman or any HTTP client. Here is documentation https://documenter.getpostman.com/view/41681143/2sB2qgdxhQ
๐ Thatโs it! Your application should now be fully operational.
When a request is made to PHP api, it goes to the index.php file which first initializes session handling and sets CORS headers to allow communication from a frontend.
Then, it includes the autoloader and instantiates controllers for users, shipments, tracking statuses, post offices, and support tickets.
Each controller is associated with various route strategies (GET, POST, PATCH, DELETE) that define the CRUD behavior.
These strategies are optionally wrapped in security decorators that enforce authorization checks (e.g., whether the user is authenticated, admin, or resource owner).
All routes are registered to a custom Router class, which matches the request URI and method to the appropriate strategy using regex patterns. Once matched, the router returns the strategy and any URI parameters.
A Dispatcher then wraps the request through middlewareโspecifically a BufferingMiddleware that manages output buffering and sets caching headers based on the response codeโbefore invoking the strategy.
If no route matches, a 404 response is returned. Strategies like GetStrategy then delegate the request to for example UserController methods like getEntityById or getEntityByEmail, which in turn interact with a UserRepository class that runs raw SQL queries via PDO against a database.
The repository hydrates or persists user data using the User model, which implements JsonSerializable to standardize JSON responses.
The security layer uses decorators to check session-based roles and permissions using a Security class. The entire request-response cycle thus flows from HTTP input to controller logic, through repository/database interaction, and finally returns a JSON-encoded HTTP response.
| Principle | Implementation |
|---|---|
| Single Responsibility Principle (SRP) | Controllers (e.g., UserController) handle request logic related to users. Repositories (e.g., UserRepository) manage DB operations only. Models (e.g., User) focus on structure and serialization. Middleware (e.g., BufferingMiddleware) handles buffering and cache headers. Security Decorators manage authorization separately. |
| Open/Closed Principle (OCP) | The use of strategy patterns (GetStrategy, AddStrategy, etc.) allows new behaviors (e.g., new request types or logic) to be added without modifying existing logic. Security decorators can wrap strategies without altering their internal implementation, extending behavior transparently. |
| Liskov Substitution Principle (LSP) | Each strategy can be used interchangeably through a common interface (likely a RouterStrategyInterface), without breaking routing or dispatch logic. All models implementing JsonSerializable can be serialized predictably, regardless of the specific model used. |
| Dependency Inversion Principle (DIP) | Controllers depend on repositories, not raw SQL or PDO directlyโthey, keeping data access loosely coupled. Routing and dispatching are handled by infrastructure-level classes, but controllers and strategies donโt depend on their implementations. Security checks are implemented via decorators, keeping business logic decoupled from authorization logic. |
| DRY | Centralized Routing: All route handling is abstracted into a single Router class using regex mapping. Middleware & Decorators: Cross-cutting concerns (e.g., caching, buffering, authentication) are handled in middleware and decorators, avoiding repetition in each strategy or controller. Repository Layer: Shared SQL logic is isolated in repositories like UserRepository, so data access code isnโt duplicated across controllers. |
| KISS | Use of Strategy Pattern: Clean separation of logic for different HTTP methods (GET, POST, etc.) makes the codebase modular and understandable. Minimal Controller Logic: Controllers delegate work to repositories and strategies, avoiding bloated methods. Custom Router and Dispatcher: While powerful, they stay simple in purposeโmatching URIs and passing them onโwithout becoming micro-frameworks. |
| Pattern | Implementation | Benefit |
|---|---|---|
| Strategy Pattern | GetStrategy, AddStrategy, UpdateStrategy, etc., define behavior for each HTTP method. | New HTTP behaviors can be added without altering existing code โ aligns with the Open/Closed Principle. |
| Decorator Pattern | SecurityDecorator wraps strategy objects to enforce authentication/authorization rules (e.g., admin check, ownership check). | Keeps security logic separate from core business logic and promotes reuse. |
| Chain of Responsibility | BufferingMiddleware wraps responses to handle output buffering and cache headers. | Decouples shared concerns like caching from route or controller logic. |
| Singleton | Security and Database has only one instance and provides a global point of access | Controlled Access to Instance |
| Repository Pattern | UserRepository, ShipmentRepository, etc., interact with the database via SQL using PDO. | Keeps controllers clean and business-focused; improves testability and abstraction over persistence. |
| Pattern | Implementation |
|---|---|
| Extract Method | Controllers for example UserController delegate tasks like retrieving a user by ID or email to separate methods like getEntityById() or getEntityByEmail(). |
| Move Method | Database logic is moved out of controllers and placed in UserRepository or other repositories. |
| Replace Conditional with Polymorphism | Use of strategies like GetStrategy, AddStrategy, UpdateStrategy avoids large conditionals for request types. |
| Encapsulate Field | All the models fields made private with added getters and setter for accessing and retrieving |
| Extract Interface | Made repositories implement generic RepositoryInterface |
| Extract Superclass | All controller extend AbstractController with generic logic |