# The Obsession with Order In the early days of a project, a controller often starts as a small, manageable file. But as features creep in—Authentication, CRUD, Admin tools, Third-party integrations—that file begins to swell. It becomes a **God Controller**: a monolithic beast that knows too much, does too much, and eventually, becomes impossible to change without fear. At **erpbsg**, our `StaffProfileController` had reached that breaking point. At **1,484 lines of code**, it wasn't just a file; it was a liability. It was time for a Reboot. --- ## The Mess: 1.5k Lines of Cognitive Noise The original controller was a textbook example of "High Coupling." It was handling everything from public account activation to sensitive administrative assignments. But the real "villain" was the **Boilerplate**. Look at this repetitive pattern that haunted every single method: ```php public function updateMe(Request $request, Response $response): Response { $userId = $request->getAttribute('user_id'); $data = $request->getParsedBody(); try { $result = $this->staffProfileService->updateStaffByUserId($userId, $data); $response->getBody()->write(json_encode($result, JSON_UNESCAPED_UNICODE)); return $response->withHeader('Content-Type', 'application/json')->withStatus(200); } catch (NotFoundException $e) { $response->getBody()->write(json_encode(['error' => $e->getMessage()], JSON_UNESCAPED_UNICODE)); return $response->withHeader('Content-Type', 'application/json')->withStatus(404); } catch (\Exception $e) { $this->logger->error("Error in updateMe: " . $e->getMessage()); $response->getBody()->write(json_encode(['error' => 'Internal Server Error.'], JSON_UNESCAPED_UNICODE)); return $response->withHeader('Content-Type', 'application/json')->withStatus(500); } } ``` Imagine this block repeated for 18 endpoints. That's nearly **600 lines of pure noise**. The business logic was drowned in a sea of `json_encode` and manual header management. --- ## The Strategy: Slices and Traits Our strategy for the Reboot was twofold: 1. **Vertical Slicing:** Instead of one massive controller, we would create four specialized "Slices" based on security contexts and responsibility: * `StaffMeController`: Self-service for authenticated staff. * `StaffActivationController`: Public token-based activation. * `AdminStaffController`: Global administrative CRUD. * `StaffAssignmentController`: Merchant-specific assignments. 2. **The Weapon: `ApiControllerTrait`:** We decided to stop repeating ourselves. We built a native Trait to handle the "plumbing" of the API, allowing our controllers to focus purely on the request/response flow. --- ## The Craft: Precision Extraction We didn't just cut and paste. We followed the **BOS Protocol**: TDD first. We kept the original test suite (17 integration tests) as our safety net while we moved logic one slice at a time. First, we extracted the "Plumbing" into a Trait: ```php trait ApiControllerTrait { protected function jsonResponse(Response $response, mixed $data, int $status = 200): Response { $response->getBody()->write(json_encode($data, JSON_UNESCAPED_UNICODE)); return $response->withHeader('Content-Type', 'application/json')->withStatus($status); } protected function handleException(Response $response, \Exception $e, LoggerInterface $logger, string $context = ''): Response { // Internal mapping of Domain Exceptions to HTTP Status Codes $status = match(get_class($e)) { ValidationException::class => 400, NotFoundException::class => 404, ConflictException::class => 409, ForbiddenException::class => 403, default => 500 }; if ($status === 500) $logger->error("Error in {$context}: " . $e->getMessage()); return $this->jsonResponse($response, ['error' => $e->getMessage()], $status); } } ``` Then, we implemented the new, ultra-lean controllers. Look at the difference in the `updateMe` method now: ```php public function updateMe(Request $request, Response $response): Response { $userId = $request->getAttribute('user_id'); $data = $request->getParsedBody(); try { $result = $this->staffProfileService->updateStaffByUserId($userId, $data); return $this->jsonResponse($response, $result); } catch (\Exception $e) { return $this->handleException($response, $e, $this->logger, 'updateMe Staff'); } } ``` The "Cognitive Noise" is gone. The intention is clear. --- ## The Result: Architectural Sovereignty By killing the God Controller, we achieved: * **Minimalism:** Deleted the 1,484-line file. Replaced it with 4 controllers averaging 120 lines each. * **Security:** Public activation logic is now physically separated from administrative tools. * **Readability:** The code is now "Art." You can understand any endpoint in less than 5 seconds of scanning. * **Confidence:** PHPStan now reports 0 errors across the new structure, and our test suite remains 100% Green. This is **Engineering as Art**. We didn't just fix a bug; we restored the architectural integrity of our ecosystem. --- **DAMMGO HOLDING** - _Engineering Longevity._