Pull to refresh

Стандарт разработки приложений на CodeIgniter

Reading time 8 min
Views 19K
А мы используем внутренние стандарты. Они действительно полезны:
стандартам совершенно не обязательно следовать;
— они позволяют быстрее и комфортнее осваиваться;
— они помогают меньше теряться при творческом процессе.

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


Контроллеры


1. Один контроллер должен отвечать за один функциональный модуль.
2. Модули с перекрестной функциональностью могут быть выделены в отдельные контроллеры, или присоединены к существующим. В обоих случаях архитектурное решение должно быть единым на весь продукт.
3. В каждом методе контроллера должно быть учтены и при возможности разделены функциональные зоны:
— права доступа;
— получение и обработка данных;
— вывод данных.
При этом допускается как применение метода _remap(), так и расширение класса контроллера, так и дублирование функциональных зон в каждом методе. Важно, чтобы действия были явно определены и разделены.
4. Не стоит использовать роутинг, если этого можно избежать.
5. Не стоит делать вывод данных из контроллера, минуя класс Output (отображения), если это не AMF.
6. Имеет смысл использовать приватные функции, для сокращения кода с идентичной функциональностью.
7. Код публичных методов контроллеров должен быть высокоуровневым, кратким и лаконичным. Все очевидно рутинные действия имеет смысл выполнять в приватных методах контроллеров, в моделях, отображениях, хелперах или библиотеках.
8. Имеет смысл использовать понятные и лаконичные имена методов, что даст красивые и очевидные URL, что помимо прочего способствует более комфортному сопровождению программы.

Модели


1. Каждая модель выполняет действия только со своей связанной сущностью.
2. При возможности каждой сущности должна соответствовать одна таблица в БД. Если для обслуживания, к примеру, связей, требуются дополнительные таблицы — имеет смысл рассмотреть вопрос: а не сделать ли связь дополнительной сущностью, со своей моделью.
3. Имеет смысл расширять класс моделей (пример внизу).
4. Имеет смысл использовать ORM.
5. Имеет смысл избегать сложных запросов в пользу серий простых запросов, позволяющих кешировать данные и выполнять более гибкие и более быстрые обработки.
6. Следует использовать Active Record.
7. Не стоит хранить в БД информацию, которая не изменяется при выполнении программы.

Отображения


1. Имеет смысл создать и использовать алиас для $this->load->view($view,$data,TRUE) в процедурном контексте (пример внизу).
2. Отображения могут загружать другие отображения. Следует использовать эту возможность:
— для организации иерархической структуры вывода
— для отделения функциональной логики вывода и рутин от шаблонов
— для отделения шаблонов дизайна. Например, это дает возможность быстро и буквально переключать дизайн
— для форматирования структурного и функционального вывода (HTML page, JSON, фрагмент HTML, динамические данные)
3. Не стоит в отображениях генерировать файлы и не динамические данные
4. В отображениях можно изменять формат представления данных. Но не стоит в отображениях изменять содержимое данных — для этого есть ООП-мощь контроллеров, моделей и библиотек, и также процедурные хелперы.
5. Не стоит в отображениях использовать JavaScript, если нет очевидной необходимости обратного.

Конфигурация


1. Имеет смысл создавать свои конфигурационные файлы, так как нативные имеют тенденцию обновляться вместе с фреймворком.
2. Имеет смысл использовать поддиректории для функциональных конфигураций.
3. Целесообразно выносить в конфигурации все специфические данные и данные, которые могут изменяться в ходе развития проекта, вместо того, чтобы явно указывать их в коде программы. Это позволяет быстрее находить их, и комфортно модифицировать.
4. Для некоторых данных конфигурации, которые могут изменяться в ходе развития проекта, имеет смысл предусмотреть денормализацию в БД для обеспечения корректности истории.

Библиотеки


1. Не стоит создавать библиотеку, если необходимость ее создания не очевидна. Например, вместо библиотеки можно создать контроллер.
2. В библиотеках используется объектный контекст, и это необходимо учитывать до ее проектирования.
Общие замечания
1. Очень полезно изучить, следовать и перечитывать не реже одного раза в год Style Guide.
2. Имеет смысл использовать имеющиеся во фреймворке решения, прежде чем создавать собственные альтернативы.
3. Фреймворк является достоверно суперлегковесным. Это подтверждено многими тестами и исследованиями. Не имеет смысла оптимизировать его.
4. Имеет смысл использовать средства отладки, встроенные во фреймворк (профайлер и режимы логгирования ошибок).
5. Имеет смысл размещать директории application и system, а также другие файлы, которые не должны быть доступны по умолчанию пользователям сайта, выше Document Root виртуального хоста.
6. Прежде чем проектировать что-то принципиально новое, имеет смысл подсмотреть в гугле, на GitHub и BitBucket реализацию аналогичных задач. С вероятностью 99% любая произвольная задача уже решена. Все тривиальные задачи имеют по нескольку вариантов решений для CodeIgniter.
7. В автозагрузке имеет смысл указывать то, что достоверно часто используется в текущем и в последующих приложениях.
8. Не стоит в автозагрузке указывать специфичные ресурсы, которые можно подгружать в конструкторах контроллеров.
9. Комментарии хороши, когда они к месту, лаконичны и написаны на английском языке.
10. TODO необходимо отмечать ключевым словом TODO.
11. Перед тем, как использовать сторонние библиотеки, убедитесь в том, что они написаны на PHP5 (а не PHP4)

Вариант расширения application/core/MY_Model.php
<?php
class MY_Model extends CI_Model 
{
	protected $_table;
	protected $_primary_key = 'id';
	protected $_active_key = 'is_active';
	function __construct()
	{
		parent::__construct();
		$this->_table = strtolower(get_class($this));
	}
	function get_count($factors = FALSE)
	{
		if ($factors)
		{
			$this->db->where($factors);
		}
		return  $this->db->
		count_all_results($this->_table);
	}
	function get_item($id)
	{
		$id = intval($id);
		if ( ! $id)
		{
			return FALSE;
		}
		return $this->db->
			where('id',$id)->
			get($this->_table)->
			row();
	}
	function get_item_by($factors) // by some parameters
	{
		if ( ! $factors)
		{
			return FALSE;
		}
		return $this->db->
			where($factors)->
			get($this->_table)->
			row();
	}
	/**
	* Get list of items 
	*
	* $all_items = $this->get_list(FALSE);
	* $all_active_items = $this->get_list();
	* $all_active_items_by_pages = $this->get_list(TRUE,$from,$limit);
	* $all_deleted_items = $this->get_list('deleted');
	* $some_items = $this->get_list(array(
		'is_active' => TRUE,
		'counter >=' => 10
		),
		$from,
		FALSE,
		'created',
		'desc'
		);
	*/
	function get_list($factors = array('is_active' => 1),$from = FALSE, $limit = FALSE,$order = FALSE,$direction = 'asc')
	{
		if ( ! is_array($factors))
		{
			if (TRUE === $factors)
			{
				$this->db->where($this->_active_key,1);
			}
			elseif ('deleted' == $factors)
			{
				$this->db->where($this->_active_key,NULL);
			}
			elseif ('active' == $factors)
			{
				$this->db->where($this->_active_key,1);
			}
		}
		elseif ($factors)
		{
			$this->db->where($factors);
		}
		if ($order)
		{
			$this->db->
			order_by($order,$direction);
		}
		if ($limit == FALSE)
		{
			return $this->db->
			get($this->_table)->
			result();
		}
		else
		{
			return $this->db->
			get($this->_table, $limit, $from)->
			result();
		}
	}
	// Get active elements 
	function get_active($from = FALSE, $limit = FALSE,$order = FALSE,$direction = 'asc')
	{
		return $this->get_list(TRUE,$from,$limit,$order,$direction);
	}
	// Get deleted elements 
	function get_deleted($from = FALSE, $limit = FALSE,$order = FALSE,$direction = 'asc')
	{
		return $this->get_list('deleted',$from,$limit,$order,$direction);
	}
	// add item from post data
	function add($fields,$data = FALSE)
	{
		foreach ($fields as $field)
		{
			$this->db->set($field['field'],$this->input->post($field['field']));
		}
		$this->accordion('add');
		return $this->insert($data);
	}
	// data insert
	function insert($data = FALSE)
	{
		if ($data)
		{
			$this->db->set($data);
		}
		$this->db->insert($this->_table);
		return $this->db->insert_id();
	}
	// edit record
	function edit($id,$fields,$data = FALSE)
	{
		foreach ($fields as $field)
		{
			$this->db->set($field['field'],$this->input->post($field['field']));
		}
		$this->accordion('edit');
		$this->update($id,$data);
	}
	// update record
	function update($id,$data = FALSE) // found by ID
	{
		$id = intval($id);
		if ( ! $id)
		{
			return FALSE;
		}
		if ($data)
		{
			$this->db->
			where($this->_primary_key,$id)->
			update($this->_table, $data);
		}
		else
		{
			$this->db->
			where($this->_primary_key,$id)->
			update($this->_table);
		}
	}
	// set records parameter
	function set_property($id, $key, $value)
	{
		$this->db->
			where($this->_primary_key, $id)->
			set($key, $value)->
			update($this->_table);
	}
	// enable item
	function enable($item)
	{
		$active_key = $this->_active_key;
		if ($item->$active_key)
		{
			return;
		}
		$primary_key = $this->_primary_key;
		$this->set_property($item->$primary_key,$active_key,1);
	}
	// delete item
	function disable($item)
	{
		$active_key = $this->_active_key;
		if ( ! $item->$active_key)
		{
			return;
		}
		$primary_key = $this->_primary_key;
		$this->set_property($item->$primary_key,$active_key,NULL);
	}

	// related items
	var $list = array();
	function set_related($data,$key = 'uid')
	{
		if ( ! $data)
		{
			return;
		}
		if (is_array($data))
		{
			if (isset($data[$key]))
			{
				$this->list[$data[$key]] = FALSE;
				return;
			}
			foreach ($data as $row)
			{
				if (is_object($row))
				{
					if (isset($row->$key))
					{
						$this->list[$row->$key] = FALSE;
					}
				}
				elseif (is_array($row))
				{
					if (isset($row[$key]))
					{
						$this->list[$row[$key]] = FALSE;
					}
				}
				elseif (is_numeric($row))
				{
					$this->list[$row] = FALSE;
				}
			}
		}
		elseif (is_object($data))
		{
			if (isset($data->$key))
			{
				$this->list[$data->$key] = FALSE;
			}
		}
		elseif (is_numeric($data))
		{
			$this->list[(int)$data] = FALSE;
		}
	}
	// load list data
	function load_related($list = FALSE,$key = 'uid')
	{
		if ($list)
		{
			$this->set_related($list,$key);
		}
		if (empty($this->list))
		{
			return;
		}
		$keys = array_keys($this->list);
		$listing = $this->db->
			where_in($this->_primary_key,$keys)->
			get($this->_table)->
			result();
		if ( ! $listing)
		{
			return;
		}
		foreach ($listing as $record)
		{
			$this->list[$record->id] = $record;
		}
	}
	// returns related items
	function related($list = FALSE,$key = 'uid')
	{
		$this->load_related($list,$key);
		return $this->list;
	}
	// set or update standart fields 
	function accordion($action = 'add')
	{
		if ( ! in_array($action,array('add','edit')))
		{
			// dont knew what to do
			return;
		}
		// get table
		// found acquaintance fields
		// make some actions
		$this->config->load('tables/'.$this->_table,TRUE);
		$fields = config_item('tables/'.$this->_table);
		if ($action == 'add')
		{
			if (array_key_exists('created',$fields))
			{
				$this->db->set('created',time());
			}
			if (array_key_exists($this->_active_key,$fields))
			{
				$this->db->set($this->_active_key,1);
			}
			if (array_key_exists('uid',$fields))
			{
				if ($this->user)
				{
					$this->db->set('uid',$this->user->profile['id']);
				}
			}
		}
		elseif ($action == 'edit')
		{
			if (array_key_exists('created',$fields))
			{
				if (array_key_exists('updated',$fields))
				{
					$this->db->set('updated',time());
				}
			}
			if (array_key_exists('changed',$fields))
			{
				$this->db->set('changed',time());
			}
		}
	}
	// counter increment
	function increment($id,$field)
	{
		$this->db->
			set($field,$field.' + 1',FALSE)->
			where($this->_primary_key,$id)->
			update($this->_table);
	}
	// counter decrement
	function decrement($id,$field)
	{
		$this->db->
			set($field,$field . ' - 1 ',FALSE)->
			where($this->_primary_key,$id)->
			update($this->_table);
	}
	// move item up
	function move_up($item,$sortorder = 'sortorder')
	{
		$active_key = $this->_active_key;
		$prev = $this->db->
			where('`'.$sortorder.'` < '.$item->$sortorder)->
			where($active_key,$item->$active_key)->
			order_by($sortorder,'DESC')->
			get($this->_table)->
			row();
		if ($prev)
		{
			$primary_key = $this->_primary_key;
			$this->db->
				set($sortorder,$prev->$sortorder)->
				where($primary_key,$item->$primary_key)->
				update($this->_table);
			$this->db->
				set($sortorder,$item->$sortorder)->
				where($primary_key,$prev->$primary_key)->
				update($this->_table);
		}
	}
	// move item down
	function move_down($item,$sortorder = 'sortorder')
	{
		$active_key = $this->_active_key;
		$prev = $this->db->
			where('`'.$sortorder.'` > '.$item->sortorder)->
			where($active_key,$item->is_active)->
			order_by($sortorder)->
			get($this->_table)->
			row();
		if ($prev)
		{
			$primary_key = $this->_primary_key;
			$this->db->
			set($sortorder,$prev->$sortorder)->
			where($primary_key,$item->$primary_key)->
			update($this->_table);
			$this->db->
			set($sortorder,$item->$sortorder)->
			where($primary_key,$prev->$primary_key)->
			update($this->_table);
		}
	}
}


С этим расширением модели выглядят аскетично, и имеют однотипный API для большей части действий.

Функция design() в application/helpers/MY_html_helper.php, которой часто не хватает.

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


Намекните, если материал понравился. Сообщите, чего не хватает, что можно дополнить.
Tags:
Hubs:
+1
Comments 25
Comments Comments 25

Articles