Хочу написать пару слов о том, каким образом функционирует back-end вывода материалов в моем блоге. Реализовано на PHP с использованием движка Codeigniter.
Все исходники, представленные ниже, доступны по лицензии MIT.
Ссылка на Github.
Ссыска на архив там же.
Итак, рассмотрим вывод материалов
example.com
example.com/page/xx
example.com/node/xx
(вывод конкретного материала)Начнем с контроллера
Node.php
<?php class Node extends CI_Controller { public function __construct() { parent::__construct(); $this->load->model('Node_model'); $this->load->library('Theme'); } public function index() { // I use in routes.php: $route['node'] = 'node/page'; } public function page($page_number = 1) { if($this->Node_model->page_number_out_of_range($page_number)) { show_404(); } $data['nodes'] = $this->Node_model->get_nodes_of_page($page_number); $data['title'] = 'My site'; $data['pagination'] = $this->Node_model->get_pagination(); $this->theme->add_body_content('node/index'); $this->theme->display($data); } public function view($id = NULL) { if($this->Node_model->node_id_out_of_range($id)) { show_404(); } $data['node'] = $this->Node_model->get_node($id); if (empty($data['node'])) { show_404(); // the node has status 0 (not published) } $data['title'] = $data['node']['title']; $this->theme->add_body_content('node/view', $data); $this->theme->display($data); } }
index()
не используется, поскольку главная страница - это по сути example.com/page/1
Манипулирует с данными модель
Node_model.php
<?php class Node_model extends CI_Model { private $nodes_per_page; private $nodes_table_name; private $site_base_url; public function __construct() { $this->load->database(); $this->load->library('pagination'); $this->nodes_per_page = 10; $this->nodes_table_name = 'table_name'; $this->site_base_url = 'http://example.com'; } private function get_node_count() { $query = $this->db->query("SELECT COUNT(*) FROM `$this->nodes_table_name` WHERE `status`= 1"); return $query->result_array()[0]['COUNT(*)']; } private function get_max_node_number() { $query = $this->db->query("SELECT MAX(`id`) FROM `$this->nodes_table_name`"); return $query->result_array()[0]['MAX(`id`)']; } private function get_max_page_number() { return 1+floor($this->Node_model->get_node_count()/$this->nodes_per_page); } public function node_id_out_of_range($id) { if(!preg_match('/^[0-9]{1,}$/', $id) || $id < 1 || $id > $this->get_max_node_number()) { return TRUE; } else { return FALSE; } } public function page_number_out_of_range($page_number) { if(!preg_match('/^[0-9]{1,}$/', $page_number) || $page_number < 1 || $page_number > $this->get_max_page_number()) { return TRUE; } else { return FALSE; } } public function get_node($id = 1) { $query = $this->db->get_where($this->nodes_table_name, array('id' => $id,'status' => 1)); return $query->row_array(); } public function get_pagination() { $pagination_config['base_url'] = $this->site_base_url.'/page/'; $pagination_config['total_rows'] = $this->Node_model->get_node_count(); $pagination_config['per_page'] = $this->nodes_per_page; $pagination_config['first_link'] = 'Первая'; $pagination_config['last_link'] = 'Последняя'; $pagination_config['use_page_numbers'] = TRUE; $pagination_config['full_tag_open'] = '<ul class="pagination">'; // Bootstrap class 'pagination' $pagination_config['full_tag_close'] = '</ul>'; $pagination_config['num_tag_open'] = '<li>'; $pagination_config['num_tag_close'] = '</li>'; $pagination_config['cur_tag_open'] = '<li class="active"><a href="#">'; // <a> to use Bootstrap class 'active' $pagination_config['cur_tag_close'] = '</a></li>'; $pagination_config['prev_tag_open'] = '<li>'; $pagination_config['prev_tag_close'] = '</li>'; $pagination_config['next_tag_open'] = '<li>'; $pagination_config['next_tag_close'] = '</li>'; $pagination_config['first_tag_open'] = '<li>'; $pagination_config['first_tag_close'] = '</li>'; $pagination_config['last_tag_open'] = '<li>'; $pagination_config['last_tag_close'] = '</li>'; $this->pagination->initialize($pagination_config); return $this->pagination->create_links(); } public function get_nodes_of_page($page = 1) { $records_before = ($page-1) * $this->nodes_per_page; $query = $this->db->query("SELECT * FROM `$this->nodes_table_name` WHERE `status` = 1 ORDER BY `id` DESC LIMIT $records_before , $this->nodes_per_page"); return $query->result_array(); } }
Замечу сразу, что в таблице table_name
должны присутствовать поля id
(первичный ключ), title
(заголовок материала), body_summary
(превью), body_value
(собственно, сам материал), created
(дата создания: Unix timestamp), status
(значения: 1 - материал опубликован, 0 - не опубликован).
Дополнительно на будущее советую завести поля changed
(дата создания: Unix timestamp) , comment
(возможность комментирования: 0 - комментировать нельзя, 1 - комментарии уже закрыты, 2 - есть возможность комментировать).
node
и field_data_body
.
Вывод данных осуществляется посредством методов библиотеки Theme
Theme.php
<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed'); class Theme { private $CI; private $templates = array(); private $head_content = array(); private $data_passed_to_head_content = array(); // parallel arrays private $body_content = array(); private $data_passed_to_body_content = array(); // parallel arrays private $alert_type_array = array(); private $alert_array = array(); // parallel arrays /* The construction below * can be used instead of parallel arrays * but I don't like complicated data organisation * private $content_to_display = array( 'head' => array( 'head_views' => array(), 'data_to_head_views' => array()), 'alerts' => array( 'head_views' => array(), 'data_to_head_views' => array()), 'body' => array( 'body_views' => array(), 'data_to_body_views' => array())); */ public function __construct() { $this->CI = &get_instance(); $this->templates['header_template'] = 'theme/header'; $this->templates['head_body_seporator'] = 'theme/head_body_seporator.php'; $this->templates['footer_template'] = 'theme/footer'; $this->templates['alert_template'] = 'theme/alert'; } public function add_head_content($view_to_add, $data_passed = NULL) { $this->head_content[] = $view_to_add; $this->data_passed_to_head_content[] = $data_passed; } public function add_body_content($view_to_add, $data_passed = NULL) { $this->body_content[] = $view_to_add; $this->data_passed_to_body_content[] = $data_passed; } public function add_alert($alert_type, $alert_message) { $this->alert_type_array[] = $alert_type; $this->alert_array[] = $alert_message; } public function display($data) { $this->CI->load->view($this->templates['header_template'], $data); foreach($this->head_content as $i => $head_content_to_display) { if($this->data_passed_to_head_content[$i] != NULL) { $this->CI->load->view("$head_content_to_display", $this->data_passed_to_body_content[$i]); } else { $this->CI->load->view("$head_content_to_display"); } } $this->CI->load->view($this->templates['head_body_seporator']); foreach($this->alert_array as $i => $alert_message) { $alert_data['alert_type_class'] = $this->alert_type_array[$i]; $alert_data['alert_message'] = $alert_message; $this->CI->load->view($this->templates['alert_template'], $alert_data); } foreach($this->body_content as $i => $body_content_to_display) { if($this->data_passed_to_body_content[$i] != NULL) { $this->CI->load->view("$body_content_to_display", $this->data_passed_to_body_content[$i]); } else { $this->CI->load->view("$body_content_to_display"); } } $this->CI->load->view($this->templates['footer_template']); } }
Конечно, "Тема" - это сильно сказано. На данном этапе библиотека манипулирует с тремя шаблонами header
, head_body_seporator
, footer
, данные которым передаются в едином массиве инициатизирующим методом display()
, и позволяет помещать views в два региона - один расположен в <head>
, другой в <body>
. Ещё есть возможнось вывести alert, для которого также существует шаблон.
theme/header.php
<!DOCTYPE html> <html lang="en"> <head> <title><?php echo $title ?></title> <meta charset="utf-8"> <!-- Latest compiled and minified CSS --> <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootswatch/3.3.5/yeti/bootstrap.min.css"> <!-- jQuery library --> <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script> <!-- Latest compiled JavaScript --> <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js"></script>
theme/head_body_seporator.php
</head> <body> <div class="container" style="background-color:#FFFFFF;"> <div class="jumbotron"> <h1 class="main-title"><a class="main-title-a" href="/">Заголовок сайта</a></h1> </div> <div class="row"> <div class="col-md-3"> <div> <h3>Левый блок</h3> <p>Содержимое</p> </div> <div class="panel panel-default"> <div class="panel-heading"><h4>Панель</h4></div> <div class="panel-body"> <p>Содержимое</p> </div> </div> </div> <div class="col-md-9">
theme/footer.php
</div> </div> <div class="row"> <div class="col-md-4">Подвал 1</div> <div class="col-md-4">Подвал 2</div> <div class="col-md-4">Подвал 3</div> </div> <div class="row"> <div class="col-md-12"> Подвал на строку </div> </div> </div> </body> </html>
theme/alert.php
<div class="alert alert-<?php echo $alert_type_class ?> fade in"> <a href="#" class="close" data-dismiss="alert" aria-label="close">×</a> <?php echo $alert_message ?> </div>
Код отображений, используемых в контроллере Node.php
node/index.php
<?php foreach ($nodes as $node): ?> <h3><a href="/node/<?php echo $node['id'] ?>"><?php echo $node['title'] ?></a></h3> <div class="main"> <?php echo $node['body_summary'] ?> </div> <p><a href="/node/<?php echo $node['id'] ?>">Читать</a></p> <?php endforeach ?> <?php echo $pagination ?>
node/view.php
<h2> <?php echo $node['title']; ?> </h2> <small> <p><span class="glyphicon glyphicon-time"></span> <?php echo date('H:i Y-m-d', $node['created']); ?> </p> </small> <?php echo $node['body_value']; ?> <h3>Комментарии.</h3>
Безусловно, представленного здесь материала будет недостаточно для создания полноценного блога, поскольку последний предполагает возможность авторизации и создания записей. Для авторизации я использую сторонний модуль (Ion Auth, если кого интересует), а создание материала особого интереса не представляет, поэтому я воздержусь от их публикации.
Спасибо за прочтение! Надеюсь, информация выше оказалась для Вас полезна =)