Как организовать шаблоны в приложении на фреймворках

Крайне мало в сети уроков как применять фреймворки. После любой CMS сложно переключить мозги на другой уровень абстракции, отвлечься от API знакомой системы. Думаю, что описание некоторых моментов, очевидных для продвинутых и гуру-разработчиков, будет полезны для получения направления развития новичкам.

Программисты, с которыми я списывался в сети, меня не понимали. То ли я плохо объяснял, что меня интересует, то ли эти, как они считают, основы очевидны. Но толковых ответов как сделать меню, как организовать шаблоны, как хранить настройки сайта, куда запихать хелпер и так далее они мне дать не смогли. Увы, приходится доходить до всего самостоятельно.

Если есть у вас замечания и дополнения, я с удовольствием выслушаю.

CodeIgniter

CodeIgniter считается самый документированным фреймворком, а его документацию ставят в пример разработчикам других фреймворков. Однако там объяснятся тонкие моменты, но это только маленькая часть мозаики.

Концепцию M-V-C я разбирать не буду, в сети и так достаточно материалов на этот счет. Я хочу поговорить о букве «V» в этой концепции.

Причем в самом CodeIgniter приводится такая конструкция как пример передачи данных из контроллера во вьюху с данными.

<?php
class Page extends CI_Controller {
        public function index()
        {
                $data['page_title'] = 'Your title';
                $this->load->view('header');
                $this->load->view('menu');
                $this->load->view('content', $data);
                $this->load->view('footer');
        }
}

И это сделано явно для запутывания новичков. Ведь если так разбить шаблоны, то в этих кусках затем сложно будет ориентироваться, особенно когда пройдет куча времени, а код документировать лениво.

В видео уроках я встретил и такое развитие этой концепции:

в контроллере:

<?php
class Page extends CI_Controller {
        public function index()
        {
                $data['page_title'] = 'Your title';
                $this->load->view('layout_v');
        }

в шаблоне layout_v.php:

<?php $this->load->view('admin/parts/page_head'); ?>
<body>
    <div>…   </div>
 <?php $this->load->view('admin/parts/page_tail'); ?>

Так становится понятнее, какой кусок за что отвечает. Но все равно кусков будет великое множество, что добавляет хаос в разработку.

Решением может стать сторонний шаблонизатор типа Blade из Laravel, отдельные шаблонизаторы Fenom, Smarty, Twig и куча других.

Честно говоря, я не вижу большого смысла в них, ведь php сам разрабатывался как продвинутый шаблонизатор. Если подойти с умом, то можно легко обойтись только им, сэкономив память и процессорное время сервера.

Итак, в drupal есть понятие «панель» — это место, куда можно что-то вывести. Выводится «блок» — оформленная информация. В WordPress это «сайдбар» и «виджет». Терминология drupal мне больше по душе (да и короче писать), дальше буду пользоваться её.

Html — это текст и только текст. Теги разметки тоже текст, который распознает браузер и отображает страницу. Так что можно сказать, что и блок, и панель — это простая переменная. И с помощью конкатенации можно в переменную панели выводить нужное количество блоков.

То есть фактически главный шаблон можно сделать такого вида:

<!DOCTYPE html>
<html lang="ru">
<head>
	<meta charset="UTF-8">
	<title><?=$title;?></title>
	<link rel="stylesheet" href="bootstrap.min.css">
</head>
<body>
	<div class="container">
		<header class="row">
			<div class="col-md-12"><?=$header;?></div>
		</header>
		<section class="row">
			<div class="col-md-8"><?=$content;?></div>
			<aside class="col-md-4"><?=$sidebar;?></aside>
		</section>
		<footer class="row">
			<div class="col-md-12"><?=$footer;?></div>
		</footer>
	</div>
</body>
</html>

Разметка специально сделана boostrap 3, который у всех на слуху, так что код понятен.

При желании, можно вынести в отдельные чанки (части) оформление шапки и подвала, вынести скрипты в конце чтобы не загромождать код через директивы php: include(), include_once().

Для отображения блоков нужны будут свои чанки, например такой для вывода верхнего меню:

<nav class="navbar navbar-inverse navbar-fixed-top">
	<div class="container">
		<div class="navbar-header">
			<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
				<span class="sr-only">Переключение навигации</span>
				<span class="icon-bar"></span>
				<span class="icon-bar"></span>
				<span class="icon-bar"></span>
			</button>
			<a class="navbar-brand" href="#">CodeIgniterPRESS</a>
		</div>
		<div id="navbar" class="collapse navbar-collapse">
			<ul class="nav navbar-nav navbar-right">
				<?php foreach($menu as $title=>$link):?>
				<li><a href="<?=$link;?>" title="<?=$title;?>"></a></li>
				<?php endforeach; ?>
			</ul>
		</div>
		<!--/.nav-collapse -->
	</div>
</nav>

Теперь разберем логику использования всего этого добра.

  • В контроллере получаем данные по всем блокам, основному контенту;
  • Для всех блоков вызываем шаблоны и сохраняем сгенерированные блоки в переменные;
  • Формируем переменные панелей из блоков;
  • Вызываем главный шаблон и передаем все переменные в шаблон.

Как это можно реализовать на практике в контроллере:

<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
 
class HomeController extends MY_Controller {
 
	public function __construct()
	{
		parent::__construct();
		// Задаем переменным начальные значения, чтобы потом не пришлось ловить ошибки
		$this->title = 'Самый лучший сайт';
		$this->load->model('article_m'); // подключаем модель статей
		$this->load->model('menu_m');    // подключаем модель меню
		.....
	}
 
	public function index()
	{
		$this->title = 'Главная страница';
		....
		// Получаем данные меню
		$data['menu'] = $this->menu->get('top');
		// Получаем данные ленты статей
		$data['articles'] = $this->article_m->get_all();
		....
		// Создаем блоки
		$b_menu = $this->load->view('part/menu', $this->data, true); // третий параметр дает вывод результата в переменную
		$b_content =  $this->load->view('part/article_list', $this->data, true);
		...
		// Формируем панели
		$data['header']  .= $b_menu;
		$data['content'] .= $b_content;
		...
		$this->load->view('template/layot_v', $this->data);
	}
}

Итак, видно как формируются блоки, как они формируются в панели и выводятся в окончательный шаблон. Это отправная точка, откуда можно начинать что-то делать. Например, дальше можно сделать массив со скриптами, которые будут подключаться в цикле, стилями. Можно сделать каскад наследования контроллеров, которые будут расширяться потомками данными, общими для каких-то частей сайтов. Это может быть обращения к файлам конфигураций, массивы блоков, хлебные крошки. В общем, есть простор для творчества.

Так же можно создать свой хелпер для вывода каких-либо функций одной командой. Это опять же может быть функция сбора меню в несколько уровней, а не как у меня в один уровень, с проверкой на текущее состояние. Может быть функция вывода в цикле подключения скриптов и стилей. Может быть вытягивание всего получившийся страницы в одну стоку для оптимизации. В общем, тут только ваша фантазия и потребности.

Kohana

В Kohana можно делать ровно тоже самое. Синтаксис будет чуть-чуть другой, но все будет точно так же по логике.

Кроме того, в Kohana есть не просто MVC, а HMVC. То есть иерархические вызовы контроллеров. Блоки в таком случае управляются отдельными контроллерами, а в главном контроллере, который получает управление, они вызываются. У контроллеров блоков будут свои шаблоны. И таким образом код будет точно еще больше изолирован.

Очень подробно этот вариант рассмотрен в видео курсе Школы Программирования «Kohana Framework от А до Я».

Laravel

В Laravel есть встроенный шаблонизатор Blade, который может многие вещи упростить.

Концепция шаблонов в Laravel такое же как и в других шаблонизаторах: есть главный шаблон, где размечены панели/области, куда будет выводиться. А контроллер вызывает шаблон, который расширяет базовый и фактически выводит в области блоки. Причем если блок пустой, то ошибки не возникает.

Вот так будет выглядеть шаблона layout.blade.php

<!DOCTYPE html>
<html lang="ru">
<head>
	<meta charset="UTF-8">
	<title>{{{ $title }}}</title>
	<link rel="stylesheet" href="bootstrap.min.css">
</head>
<body>
	<div class="container">
		<header class="row">
			<div class="col-md-12">@yield('header')</div>
		</header>
		<section class="row">
			<div class="col-md-8">@yield('content')</div>
			<aside class="col-md-4">@yield('sidebar')</aside>
		</section>
		<footer class="row">
			<div class="col-md-12">@yield('footer')</div>
		</footer>
	</div>
</body>
</html>

А контроллер уже будет вызывать другой шаблон, расширяющий этот:

@extends('layout')
 
@section('header')
 @if (count($menu))
   <nav class="navbar navbar-inverse navbar-fixed-top">
	<div class="container">
		<div class="navbar-header">
			<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
				<span class="sr-only">Переключение навигации</span>
				<span class="icon-bar"></span>
				<span class="icon-bar"></span>
				<span class="icon-bar"></span>
			</button>
			<a class="navbar-brand" href="#">LaravelPRESS</a>
		</div>
		<div id="navbar" class="collapse navbar-collapse">
			<ul class="nav navbar-nav navbar-right">
				@foreach($menu as $title=>$link)
				<li><a href="{{{ $link }}}" title="{{{ $title }}}"></a></li>
				@endforeach
			</ul>
		</div>
		<!--/.nav-collapse -->
	</div>
    </nav>
 @endif
@endsection
 
@section('content')
  @if(count($article))
     <article class="card post">
              <div class="card-header">
                <h3 class="post-title"><a href="single.html" class="transicion">Lorem ipsum Minim Ut in nulla labore sint nostrud</a></h3>
              </div>
              <div class="card-block">
                <div class="row">
                  <div class="col-lg-4">
                      <img src="http://placehold.it/210x105" class="img-post img-responsive" alt="Image">
                  </div>
                  <div class="col-lg-8 post-content">
                      <p>Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo.</p>
                      <a href="#" class="btn btn-primary pull-right">Читать дальше &raquo;</a>
                  </div>
                </div>
              </div>
              <div class="card-footer text-muted">
                 <p class="meta-post">
                  <i class="fa fa-calendar"></i> 15 сентября 2015 |  
                  <i class="fa fa-folder-open"></i> <a href="#">Верстка</a>, <a href="#">Марафон</a>. 
                  <span class="pull-right">
                     <i class="fa fa-eye" aria-hidden="true"></i> 887
                    <a href="#comments"><i class="fa fa-comments"></i> 0</a>
                  </span>
                </p>
              </div>
            </article>
  @elseif
    <h1>Статей нет!</h1>
  @endif
 
@endsection
 
@section('sidebar')
 
@endsection
 
@section('footer')
 
@endsection

Я не стал тут расписывать как все вывести досконально, тут и так понятна мысль. Логика простая: проверяется данные и если не пустые переменные с массивами, то выводится шаблон с данными, иначе сообщение что ничего не передано.

В контроллере тоже можно точно так же получить все необходимые данные и аккуратно их передать во вьюху.

При наличие желания как можно лучше все оптимизировать, можно вынести в хелперы опять же функции вывода меню, шаблоны вывода блоков тоже вынести в меню и сократить код до маленького удобочитаемого шаблона.

Заключение

Документация — это важная часть любого фреймворка. Но думать и анализировать своей головой тоже нужно много, а не тупо копипастить примеры из документации с надеждой, что заработает. Все эти прописные истины, так же как и такой простой код приходит со временем и опытом.

Можно попенять, что я поленился и показал прописные истины, а вот если бы я сделал библиотеку шаблона сайта именно как шаблона и т.д. и т.п. Но цель статьи была в том, чтобы показать откуда начать свое путешествие в разработку с помощью фреймворков. Более сложные вещи находятся и адаптируются по мере необходимости. Делать сложную систему шаблонизации сайта смысла нет: через время нужно сайт будет расширять и менять шаблон, а это проще сделать так, чем изобретать свою сложную систему шаблонизации а-ля Joomla, Drupal или WordPress. Все-таки сайт — это не готовая CMS на все случаи жизни. Точно так же во многом не стоит делать меню как отдельный модуль, а проще прописать один раз в шаблоне. Но об этом уже в другой статье.

Очень много в сети пустых споров о том, какой фреймворк круче, лучше, быстрее и где можно больше заработать деньги. Увы, все эти критерии относятся к разным продуктам. Нет идеального фреймворка, который стоит изучить и все на этом. Позавчера рулил CodeIgniter, который вот уже года 4 хоронят, а он все живее всех живых. Вчера рулил Kohana, который в 2014 году тоже вроде как закончил свой путь, однако в марте вышел новый минорный релиз 3.3.5 и поговаривают о 4 ветке, хотя нет до сих пор ни строчки кода. Сейчас рулит Laravel 5. И это только те фреймворки, которые я отслеживаю. FuelPHP тихо умирает, новых версий не было давно, в России и пост-СССР еще распространен YII, который мне как-то не слишком понравился.

Так вот, самый быстрый и простой CodeIgniter. Чуть сложнее и удобнее Kohana. А уж Laravel самый тяжелый и толстый. Но удобнее в разработки все в обратной последовательности. И в синтаксисе есть свои плюсы и минусы, и в документации.

Поэтому не нужно зацикливаться на чем-то одном и быть его апостолом/фанатом. Выбирайте инструмент по руке.

Ну а на этом я прощаюсь. Думаю следующую статью написать о проектировании баз для сайтов.

Ссылка на основную публикацию