Дайте ссылку на "канонический" образец MVC
MVC образца 1979-ого года (канонический) подразумевает то, что контроллер ничегошеньки не знает о view. Он ловит события с инпутов и конвертит асинхронные действия пользователей в вызовы методов модели. Имеется в виду модель нашей логики, модель приложения, не обязательно один класс но целая иерархия которая сама может включать сколько угодно слоев и иметь сколь угодно большую сложность. Контроллер детали реализации модели вообще не парит, инкапсуляцию для этого придумали.
Так вот, View же напрямую подключается к модели и через обсервер подписывается на обновления состояния модели, и в случае оного актуализирует себя под текущее состояние.
Ну это если мы говорим про олдскульный MVC который в чистом виде никто не применяет уже лет 15-20. Ну и на бэкэнде в этом нет особо смысла так как модель в рамках одного запроса-ответа поменяться дважды у нас не должна. Просто пробрасываем все необходимое текущее состояние во view и все хорошо.
В целом у нас всеравно есть зависимость view от модели, что не ок. Потому чуваки придумали
Model View Adapter (можно считать это вариацией MVP но есть нюансы).
Суть такая. В качестве адаптера сделаем контроллер, который будет получать данные формата UI (HTTP запрос в нашем случае) и будет генерировать данные для UI (HTTP ответ опять же). То есть задача контроллера сводится всего-лишь к тому что бы получить запрос, дернуть метод модели (один в идеале) и сформировать ответ.
Итог - полная независимость представления от модели и модели от представления. Конвертацией форматов орудует адаптер (в нашем случае это GRASP контроллер). Причем мы можем выстраивать целую цепочку адаптеров (концепция мидлвэров на этом строится), которую потом можно свести к одному главному фронт-контроллеру. Ну и подходит это не только для HTTP но и для всяких там MQ/CLI и других вариантов интерфейсов которые могут пригодиться в будущем (а могут и не пригодиться).
В случае с рендрингом стоит вынести это добро в отдельный компонент, что бы в контроллере можно было бы это все сделать вызовом одного-двух методов.
Ну и про буферизацию вывода не забываем.
p.s. хватит писать велосипеды, вы всеравно из этого усвоите мало чего. Возьмите какой-нибудь микрофреймворк, что бы можно было и в нутрах покопаться, и бойлерплейт пописать, и на основе уже готового подумать почему там так сделано.