Недостатки View в Code Igniter
Ни для кого не секрет, что CodeIgniter использует концепцию MVC (Model-View-Controller). View или Представление — отвечает за отображение данных пользователю. Главным преимуществом концепции MVC является разделение логики управления приложения, получения данных и их отображения. Начиная работать с CodeIgniter, вы уходите от мешанины из HTML/PHP/SQL в одном месте (мне до сих пор встречаются файлы-модули на 5000-7000 строк с адскими функциями в пару тысяч строк). Работая с View, вы не должны обращаться к моделям напрямую. Это должно быть аксиомой для вас. Всё что вы можете во View — это обработать входящие данные из Контроллера и использовать helper‘ы.
Давайте посмотрим недостатки базовой реализации View. Для примера возьмем кусочек из документации: CodeIgniter › User Guide › Views
<?php class Page extends CI_Controller { function index() { $data['page_title'] = 'Your title'; $this->load->view('header'); $this->load->view('menu'); $this->load->view('content', $data); $this->load->view('footer'); } } ?> |
В большинстве случаев мы делим шаблон на 3 части: контент (который меняется в зависимости от страницы), всё, что до него (header, заголовки, меню, шапка и т. д.) и всё, что после него (footer, подвал, меню, контакты и т д).
Меню, обычно, нужно получить из базы данных. Это делается примерно так:
class Page extends CI_Controller { function index() { $menu['top'] = $this->menu_model->get_top_menu(); $menu['side'] = $this->menu_model->get_side_menu(); $data['page_title'] = 'Your title'; $this->load->view('header', $menu); $this->load->view('content', $data); $this->load->view('footer', $menu); } } ?> |
А потом нужно отобразить то же самое меню, но на другой странице:
class Page extends CI_Controller { function index() { $menu['top'] = $this->menu_model->get_top_menu(); $menu['side'] = $this->menu_model->get_side_menu(); $data['page_title'] = 'Last 10 pages'; $data['page_list'] = $this->page_model->get_list(10); $this->load->view('header', $menu); $this->load->view('content', $data); $this->load->view('footer', $menu); } function show($id = NULL) { $menu['top'] = $this->menu_model->get_top_menu(); $menu['side'] = $this->menu_model->get_side_menu(); $id = (int) $id; $data['page_title'] = 'Page #'.$id; $data['page'] = $this->page_model->get_page($id); $this->load->view('header', $menu); $this->load->view('content', $data); $this->load->view('footer', $menu); } } ?> |
В итоге в функции контроллера из 8 строк — 5 занимаются получением однотипных данных и управлением отображениями! И это простой пример. А что, если у вас в боковом блоке и подвале на каждой странице выводится:
- меню,
- список категорий,
- облако тегов,
- последние 10 комментариев,
- ссылки на дружественные ресурсы,
- архив по месяцам,
- топ статей,
- топ комментаторов.
Контроллер в итоге будет иметь вид:
class Page extends CI_Controller { function index() { $menu['top'] = $this->menu_model->get_top_menu(); $menu['cats'] = $this->category_model->get_list(); $menu['tags'] = $this->tag_model->get_list(); $menu['last_comments'] = $this->comment_model->get_last(10); $menu['links'] = $this->link_model->get_list(); $menu['month_pages'] = $this->page_model->get_month_list(); $menu['top_pages'] = $this->page_model->get_top(10); $menu['top_comments'] = $this->comment_model->get_top(10); $data['page_title'] = 'Last 10 pages'; $data['page_list'] = $this->page_model->get_list(10); $this->load->view('header', $menu); $this->load->view('content', $data); $this->load->view('footer', $menu); } } ?> |
И так — в каждой функции каждого контроллера.
Многие новички, доходя до этого момента, просто впадают в ступор.
Варианты решения, которые приходят в голову:
1. Использование хелперов.
Создадим хелпер:
application\helpers\menu_helper.php
<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed'); if ( ! function_exists('draw_top_menu')) { function draw_top_menu() { $CI =& get_instance(); $query = $CI->db->query("SELECT id, url, name FROM menu ORDER BY id ASC"); $result = array(); if ($query->num_rows() != 0) { $result = $query->result_array(); } $temp = '<ul>'; foreach ($result as $val) { $temp.= '<li>'.anchor($val['url'], $val['name']).'</li>'; } $temp.= '</ul>'; return $temp; } } |
И в самом отображении добавим вызов этого хелпера:
application\views\header.php
<html> <head> <body> <div id="topmenu"> <?php echo draw_top_menu(); ?> </div> |
В результате получим:
<html> <head> <body> <div id="topmenu"> <ul> <li><a href="http://localhost/">HOME</a></li> <li><a href="http://localhost/news">NEWS</a></li> <li><a href="http://localhost/page/about">ABOUT</a></li> </ul> </div> |
Не самый элегантный и красивый метод. Для чего мы пришли к CodeIgniter? Чтобы навести порядок в своем коде и уйти от мешанины из SQL/PHP/HTML. А в итоге к этому и вернулись.
2. Расширение базового контроллера.
Путь по которому я пошел 3 года назад, когда изучал CodeIgniter. Суть метода заключается в выносе повторяющегося куска кода в один метод базового контроллера, который и будет отвечать за отрисовку страницы. Все контроллеры будут наследоваться от одного базового. А для отображения View мы будем пользоваться новым методом _render.
application\core\MY_Controller.php
<?php class MY_Controller extends CI_Controller { public function __construct() { parent::__construct(); } final public function _render($view = 'content', $data = array()) { $menu['top'] = $this->menu_model->get_top_menu(); $menu['cats'] = $this->category_model->get_list(); $menu['tags'] = $this->tag_model->get_list(); $menu['last_comments'] = $this->comment_model->get_last(10); $menu['links'] = $this->link_model->get_list(); $menu['month_pages'] = $this->page_model->get_month_list(); $menu['top_pages'] = $this->page_model->get_top(10); $menu['top_comments'] = $this->comment_model->get_top(10); $this->load->view('header', $menu); $this->load->view($view, $data); $this->load->view('footer', $menu); } } |
application\controllers\page.php
class Page extends MY_Controller { function __construct() { parent::__construct(); } function index() { $data['page_title'] = 'Last 10 pages'; $data['page_list'] = $this->page_model->get_list(10); $this->_render('content', $data); } function show($id = NULL) { $id = (int) $id; $data['page_title'] = 'Page #'.$id; $data['page'] = $this->page_model->get_page($id); $this->_render('page/show', $data); } } |
Разберем по порядку. Конструкция: final public function _render:
final — не дает переопределить метод _render в потомках класса MY_Controller. Это защита от случайного изменения. Если вдруг надо создать другой базовый шаблон, то можно создать новый метод: _render_ajax(), _render_admin() и так далее.
public — делает метод доступным вне класса.
_render — нижнее подчеркивание перед именем метода позволяет создать защищенный метод CodeIgniter, который нельзя вызывать в браузере.
В итоге мы получили более чистый код в контроллерах и управление шаблоном вывода в одном месте. Но это тоже не идеальный вариант, хотелось бы чего-то большего. Недаром почти во всех CMS или проектах на CodeIgniter разработчики первым делом пишут/качают решения для замены стандартного механизма работы с View.
В следующей части мы сделаем обзор готовых решений для более легкого и изящного управления View.
Посмотрите в сторону: $this->load->vars($data);
Это сделает $data доступным из всех view загружаемых без указания передаваемых данных.
Перед этим делайте $data[‘content_view’]=’some/name»;
и смело всегда вызывайте одну и ту же view, допустим main.
внутри main пишите
$this->load->view(‘header’);
$this->load->view($content_view)’;
$this->load->view(‘footer’);
@IAD
Спасибо за совет
Готовится вторая часть, в которой будет обзор библиотек-шаблонизаторов под Code Igniter
ок. почитаем.
Сделайте рассылку комментариев к посту по email. Вы же зачем-то собираете емайлы =)
Уже четыре месяца осваиваю codeigniter. До всех вышеописанных методов дошел сам. Рад что это распространенная практика. Спасибо за статью =)
А про layout вы никогда не слышали? В таком случае, это ваш недостаток.
О каком layout вы говорите? Из коробки ничего подобного нет. Обзор альтернативных библиотек для расширения View я уже делал.
Сделал всё как в данном примере. В итоге ничего не работает. Во всех блоках пишет неопределённые переменные и всё.
Скорее всего вы используете CodeIgniter 2. В статье написаны примеры и идеи для первой ветки.
Советую посмотреть в сторону отдельных библиотек: Обзор библиотек для замены View в Code Igniter
Да, так и есть, версия 2.1.3. Библиотеку скачал, установил, CodeIgniter-Layout (sparks), но как ей пользоваться не понимаю и для чего она по сути нужна тоже не до конца ясно. Для меня стоит только одна задача, избавиться от одних и тех же записей типа:
$data[‘pages’] = $this->pages_model->get_pages();
$data[‘pages_info’] = $this->pages_model->get_pages_info($title);
$data[‘category’] = $this->pages_model->get_category($title);
во всех функциях контроллероВ. Проблему с шаблонами я уже давно решил, в плане загрузки видов. Осталось только такая проблема с видами. Как я понимаю, можно пойти по пути того, что каким-то образом передать свойства методов одного класса в другой класс, как в данном случае в вашем примере. Но как это реализовать до конца непонятно. А времени нет, чтобы по несколько месяцев или по пол года сидеть изучать какие-то библиотеки для непонятных шаблонов, которые в дальнейшем возможно и не пригодятся мне на практике.
Сделайте в базовом контролере свойство
$data
В конструкторе контроллеров производите присваивание:
$this->data['pages'] = $this->pages_model->get_pages();
$this->data['pages_info'] = $this->pages_model->get_pages_info($title);
$this->data['category'] = $this->pages_model->get_category($title);
И передавайте вместо
$data $this->data
в шаблон.Немного недопонял,Сделайте в базовом контролере свойство $data. Это свойство делать в MY_Controller и потом наследовать в других контроллерах класс с этим свойством? И как сделать это свойство? Ведь свойство должно быть присуще какому-то объекту. А если не создавать объект, то как создать свойство. Объясните пожалуйста.
@Александр
В CI все объекты создаются автоматически (через автозагрузку), вам не нужно об этом думать.
Сделать примерно так:
Финал! Спасибо! Всё заработало!
У меня тоже что-то наподобие получилось:
Все просто, комменты, считаю, не нужны, кроме хелпера design. Подсмотрел его где то на хабре, очень удобно, чтобы не писать каждый раз $this->load->view($view, $data, true), когда мне надо получить вывод в строку, а не в поток: