Vasilesk World

Вывод материалов в моем блоге. Back-end.

Василий Боднарюк

Хочу написать пару слов о том, каким образом функционирует 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 - есть возможность комментировать).

Оффтоп: данный список составлен из полей, присутствующих в CMS Drupal в таблицах 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, если кого интересует), а создание материала особого интереса не представляет, поэтому я воздержусь от их публикации.

Спасибо за прочтение! Надеюсь, информация выше оказалась для Вас полезна =)



A PHP Error was encountered

Severity: Core Warning

Message: Module 'pgsql' already loaded

Filename: Unknown

Line Number: 0

Backtrace: