Пользователь
–4,0
рейтинг
1 февраля 2013 в 10:11

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

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

Исторически свершилось так, что мы активно используем 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);
}


Намекните, если материал понравился. Сообщите, чего не хватает, что можно дополнить.
Артем @customtema
карма
–4,8
рейтинг –4,0
Пользователь
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама

Самое читаемое Разработка

Комментарии (25)

  • –5
    Первое правило CodeIgniter — не использовать CodeIgniter. :-)
    • 0
      Новый развивающийся сайт на CodeIgniter.
      Кстати для программистов вроде Вас тоже будет полезен.
      snippets.su
  • 0
    Не работаю с CodeIgniter, но по приведенному коду:

    1. не проще ли в set_related сделать приведение дата к массиву в самом начале и избавиться от дублирования кода?
    чтобы получилось что-то вроде (форматирование кода свое использую, т.к. запись компактнее получается и на мой взгляд так читабельнее, чем когда каждая фигурная скобка на новой строке)
    function set_related($data, $key = 'uid')
    {
        if (!$data) {
            return;
        }
        
        $data = (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;
            }
        }
    }
    


    2. $CI =& get_instance(); вы работаете с php4?
    • +1
      Не проще, потому что set_related() позволяет передавать данные в различных форматах:
      — массив массивов
      — массив объектов
      — объект
      — массив
      — целочисленное значение

      PHP4 не при делах, это просто оптимизация. $CI =& get_instance(); используется для банальной экономии памяти в хелперах, когда необходимо ссылаться на объектную составляющую в процедурной парадигме.

      Разницу можно заметить при тестировании. В нормальном среднем случае потребляется 3 МБ на запрос. Если не ловить инстанцию ссылкой — плюс 3 МБ на каждый вызов функции в спорном окружении. В высоконагруженной системе может приводить к фатальным потерям и падениям PHP.
      • +1
        а то что объекты и так передаются по ссылкам в php5 вас не смущает?
        для стандартных типов еще можно было бы так экономить (и то, только если этот $CI где то изменяется в дальнейшем, т.к. в php используется copy-on-write
        • –2
          Не смущает, потому что это работает только внутри объектов. В процедурной парадигме, при необходимости ссылаться на объект — по умолчанию создается копия инстанции, а не ссылка на нее.

          Проверить очень просто — тестом.

          $CI — это суперобъект программы, немаленький.
          • +2
            Приведите пример, где создается копия объекта, без явного вызова clone
            • –2
              В хелпере, если вместо =& сделать =.

              Вы знаете, как из процедурного окружения получить доступ к свойствам объекта?
              • +1
                1. Что вы подразумеваете под процедурным окружением?
                2. Приведите простой php код, который покажет разницу в использовании памяти между =& и = при работе с объектами

                п.с. я не придираюсь — просто хочу понять, кто из нас заблуждается
                • –1
                  1. Код вне класса
                  2. См. выше
                  3. Объективно «понять» можно только в результате теста. Попробуйте, это несложно и весьма полезно. Поделитесь результатами — отличная тема для поста!

                  Может быть, и я отстал от тенденций. На самом деле, зачастую нет ни возможности, ни желания заниматься улучшением всего подряд — особенно тогда, когда его много, оно работает, и работает хорошо.
                • +2
                  пример подойдет?
                  ini_set('error_reporting', E_ALL & ~E_STRICT);
                  
                  class A {
                      protected $arr;
                      public function __construct() {
                          $this->arr = str_repeat('abc', 1000);
                      }
                  }
                  
                  $instance = new A();
                  
                  function get_instance() {
                      global $instance;
                      return $instance;
                  }
                  
                  function foo() {
                      $mem = memory_get_usage();
                      $arr = array();
                      for ($i = 0; $i < 1000; $i++) {
                          $arr[$i] = get_instance();
                      }
                      echo memory_get_usage() - $mem, "\n";
                  }
                  
                  function bar() {
                      $mem = memory_get_usage();
                      $arr = array();
                      for ($i = 0; $i < 1000; $i++) {
                          $arr[$i] =& get_instance();
                      }
                      echo memory_get_usage() - $mem, "\n";
                  }
                  
                  foo();
                  bar();
                  


                  результат:
                  144488
                  145088
                  • +1
                    кроме того, при включенном E_STRICT вы увидите ошибку
                    Strict Standards: Only variables should be assigned by reference
                  • –1
                    В «class A» засуньте CodeIgniter с полдюжиной моделей, данными и страницей на 100 КБ. Тогда будет TRUE, и вы меня поймете :)
                    • +1
                      Не имеет значения. Объекты в php5 передаются по ссылке. Пусть хоть 100мб весит объект — в php5 не создается копия объекта без явного вызова clone
                      • 0
                        Речь идет об оптимизации посредством экономии памяти. Масштабы и задачу обозначил. Специфику тоже. Тест некорректный, хотя результаты вы получили очевидные — разница в 1 килобайт, которая может стать разницей в n * 3 мегабайта, где n в общем случае от 1 до 8, а с HMVC до 200.

                        Если исходные данные «не имеют значения» — не вижу смысла продолжать обсуждение. Извините :)
                        • 0
                          Заметьте что на 1килобайт больше получилось при использовании & ;)

                          Еще раз говорю — либо приведите конкретный контрпример, либо изучайте теорию
                        • 0
                          сделайте в классе A str_repeat не 1000, а 1000000 раз. вы получите те же цифры:
                          144472
                          145088
  • –1
    del
  • +1
    Жаль, что когда использовал CI, не додумался весь функционал такого рода вынести в MY_Model.
  • 0
    CI для того и придумали и его сильная сторона, что он гибок донельзя. Гибкость не очень согласуется со стандартизацией.
    • 0
      Гибкость — основа успешной стандартизации.

      Я в свое время немало поработал над разными стандартами. За свои слова отвечаю.
  • 0
    А можно, можно и я попридераюсь к коду? :) Заранее прошу прощения за оформление — теги не могу использовать.

    if ($factors)


    Такой код достаточно тяжело читается, т.к. $factors воспринимается как Boolean — что не есть правда.
    if (!empty($factors)) — гараздо более читабельно.

    return $this->db->
    count_all_results($this->_table);
    такие конструкции тяжело читаемы, особенно если выполненны на одном «уровне». Такое ощущение, что у Вас после return — строка кода.

    function get_list()
    Слишком много параметров (попробуй вспомни, что за чем идёт). По сути это не функция, а три или четыре функции, обьеденённые под одним именем, и доступ к ним осуществляется подбором параметров. Параметры не имееют точного типа.

    перечисленные проблемы не единичны, и встречаются даже в указанном куске кода постоянно

    • 0
      А можно, можно и я попридераюсь к коду?


      Может быть для начала родной язык выучить?
      • 0
        Может быть для начала родной язык выучить?

        Может для начала его профиль посмотреть?
        • 0
          Лето кончилось… :)

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.