Встречал "сайты", где весь код упакован в километровую простыню с кучей if/case, внутри которых и обрабатывались те или иные запросы. В целом это плохо по многим причинам, одна из которых - в интерпретатор при каждом запросе вгружается очень много кода, который надо распарсить и пр., и 99% которого не используется принципиально.
Чтобы подобные безобразия пресечь на корню, принято функциональные блоки выносить в отдельные "модули", и подгружать на конкретный запрос только их, в купе с ядром и стандартными библиотеками (что по сути можно назвать "фреймворк").
Так вот, чтобы ядро могло разрулить на какие запросы какие функциональные блоки (контроллеры) подгружать для обработки этих самых запросов, логика маршрутизации и выносится в роутер.
Для своего движка я сделал предельно простой роутер, который на входе ждет название модуля и его экшена, которому необходимо передать обработку запроса, причем модуль, по сути - это просто папка внутри папки modules, а экшен - это некая папка внутри папки модуля, т.е. modules/[module]/[action]/[action].php, соответственно модуль/экшен добавляется в проект через создание пары папок и файла. Все остальные параметры ЧПУ передаются в экшен в переменной GET.
Таким образом все что делает роутер - генерирует путь, и проверяет, есть ли там соответствующий файл PHP, и если есть - подгружает его и передает ему управление.
Если роутер не получил название экшена и.или модуля, то подставляется default, если по указанному пути файл не обнаружен, то выдается 404. Предельно просто и прозрачно, и всегда наверняка знаешь где и что лежит и как называется и почему.
Подобный подход практикуется в JavaScript фреймворке Ember.JS, под подходом я подразумеваю весьма жесткие соглашения относительно структуры и именования папок, и файлов проектов.
Шаблоны в моих проектах точно так же находятся в строго определенных местах и подключаются экшенами автоматически, если тип их выдачи HTML, иначе генерится JSON, или что-то еще, имеется несколько конвертеров на выходе, подключается тот, который запросил экшен.