log[] = 'before'; } protected function after(): void { $this->log[] = 'after'; } public function indexAction(): string { $this->log[] = 'action'; return 'body'; } } class ControllerTest extends TestCase { public function testExecuteActionReturnsActionBody(): void { $controller = new LifecycleController(); $this->assertSame('body', $controller->executeAction('indexAction')); } public function testHooksRunAroundActionInOrder(): void { $controller = new LifecycleController(); $controller->executeAction('indexAction'); $this->assertSame(['before', 'action', 'after'], $controller->log); } public function testDefaultHooksAreNoop(): void { // Голое ядро без переопределения хуков просто возвращает тело. $controller = new class extends BaseController { public function pingAction(): string { return 'pong'; } }; $this->assertSame('pong', $controller->executeAction('pingAction')); } }