Недостатки View в Code Igniter

12th Август 2011 | Категории: Code Igniter, PHP | Метки: ,

Ни для кого не секрет, что 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.

Subscribe without commenting


  1. 24th Август 2011 в 23:05

    Посмотрите в сторону: $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’);

  2. Тарлюн Максим
    24th Август 2011 в 23:14

    @IAD
    Спасибо за совет

    Готовится вторая часть, в которой будет обзор библиотек-шаблонизаторов под Code Igniter

  3. 26th Август 2011 в 15:12

    ок. почитаем.
    Сделайте рассылку комментариев к посту по email. Вы же зачем-то собираете емайлы =)

  4. 16th Июнь 2012 в 19:00

    Уже четыре месяца осваиваю codeigniter. До всех вышеописанных методов дошел сам. Рад что это распространенная практика. Спасибо за статью =)

  5. Andrew
    22nd Ноябрь 2012 в 20:07

    А про layout вы никогда не слышали? В таком случае, это ваш недостаток.

  6. Тарлюн Максим
    22nd Ноябрь 2012 в 20:52

    О каком layout вы говорите? Из коробки ничего подобного нет. Обзор альтернативных библиотек для расширения View я уже делал.

  7. Александр
    23rd Май 2013 в 14:08

    Сделал всё как в данном примере. В итоге ничего не работает. Во всех блоках пишет неопределённые переменные и всё.

  8. Тарлюн Максим
    23rd Май 2013 в 14:15

    Александр :

    Сделал всё как в данном примере. В итоге ничего не работает. Во всех блоках пишет неопределённые переменные и всё.

    Скорее всего вы используете CodeIgniter 2. В статье написаны примеры и идеи для первой ветки.

    Советую посмотреть в сторону отдельных библиотек: Обзор библиотек для замены View в Code Igniter

  9. Александр
    23rd Май 2013 в 14:45

    Да, так и есть, версия 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);
    во всех функциях контроллероВ. Проблему с шаблонами я уже давно решил, в плане загрузки видов. Осталось только такая проблема с видами. Как я понимаю, можно пойти по пути того, что каким-то образом передать свойства методов одного класса в другой класс, как в данном случае в вашем примере. Но как это реализовать до конца непонятно. А времени нет, чтобы по несколько месяцев или по пол года сидеть изучать какие-то библиотеки для непонятных шаблонов, которые в дальнейшем возможно и не пригодятся мне на практике.

  10. Тарлюн Максим
    23rd Май 2013 в 14:50

    Александр :

    Для меня стоит только одна задача, избавиться от одних и тех же записей типа

    Сделайте в базовом контролере свойство $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 в шаблон.

  11. Александр
    23rd Май 2013 в 15:00

    Немного недопонял,Сделайте в базовом контролере свойство $data. Это свойство делать в MY_Controller и потом наследовать в других контроллерах класс с этим свойством? И как сделать это свойство? Ведь свойство должно быть присуще какому-то объекту. А если не создавать объект, то как создать свойство. Объясните пожалуйста.

  12. Тарлюн Максим
    23rd Май 2013 в 15:04

    @Александр
    В CI все объекты создаются автоматически (через автозагрузку), вам не нужно об этом думать.

    Сделать примерно так:

    class MY_Controller extends CI_Controller {
     
     	public $data = array();
     
     	public function __construct()
    	{
    		parent::__construct();
    		$this->data['pages'] = $this->pages_model->get_pages();
    		$this->data['pages_info'] = $this->pages_model->get_pages($title);
    		$this->data['category'] = $this->pages_model->get_category($title);
     
    	}
     
    }
     
    class Page extends MY_Controller {
    	function __construct()
    	{
    		parent::__construct();
    	}
     
    	function index()
    	{
    		$this->data['page_title'] = 'Last 10 pages';
    		$this->data['page_list'] = $this->page_model->get_list(10);
     
    		$this->load->view('content', $this->data);
    	}
    }
  13. Александр
    23rd Май 2013 в 16:26

    Финал! Спасибо! Всё заработало!

  14. alex
    5th Сентябрь 2014 в 10:31

    У меня тоже что-то наподобие получилось:

    Base_Controller extends CI_Controller
    Base_User_Controller extends Base_Controller
    Base_Admin_Controller extends Base_Controller
     
    # Base_Controller:
    
    class Base_Controller extends CI_Controller
    {
        protected $title_page, $description, $content, $js_css_head, $menu_view, $data, $page_view, $data_before, $page_view_content;
     
        protected function out_page()
        {
            $this->before();       
            $this->data['title_page'] = $this->title_page;
            $this->data['description'] = $this->description;
            $this->data['js_css_head'] = $this->js_css_head;
            $this->data['content'] = $this->content;
            $this->load->view($this->page_view, $this->data);       
        }    
     
        protected function before()
        {
            $this->content = design($this->page_view_content, $this->data_before);
        }    
    }

    Все просто, комменты, считаю, не нужны, кроме хелпера design. Подсмотрел его где то на хабре, очень удобно, чтобы не писать каждый раз $this->load->view($view, $data, true), когда мне надо получить вывод в строку, а не в поток:

    function design($view, $data = false)
    {
        $CI = &get_instance();
        return $CI->load->view($view, $data, true);
    }