Я кодирую небольшой учебный проект CMS, и я ударил кирпичную стену, которая останавливает меня, чтобы завершить следующий шаг. Я знаю, что я должен учитывать KISS (Keep It Simple, Stupid), но я думаю, что было бы хорошо, чтобы иметь возможность группировать иерархии страниц.
Проблема в том, что я хочу, чтобы страница [root]->fruits->tropical->bananas
была доступна только из этого URL: http://localhost/cms/fruits/tropical/bananas/
. До сих пор я пришел к выводу, что таблица cms имеет родительское поле, указывающее на его родительский элемент. Возникает вопрос: как разобрать адрес uri и выбрать строку из БД с минимальным количеством запросов / эффективно?
Table structure: Id Slug ... ... ... ParentId
Вся помощь и советы любезно приняты.
Вот структура таблицы, которую я использовал для тестирования этого –
CREATE TABLE `test`.`pages` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `slug` varchar(45) NOT NULL, `title` varchar(45) NOT NULL, `content` text NOT NULL, `parent_id` int(10) unsigned DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `UQ_page_parent_id_slug` (`parent_id`,`slug`), CONSTRAINT `FK_page_parent_id` FOREIGN KEY (`parent_id`) REFERENCES `pages` (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
Обратите внимание на уникальный ключ (parent_id, slug). Это ключ к получению наилучшей производительности из следующего запроса. Я тестировал это с 50 тыс. Строк, и он все еще возвращался менее чем за 1 мс для пятерки пути – /cms/slug-1/slug-2/slug-3/slug-4/slug-5/
Вот PHP-код, который я придумал для создания подходящего запроса –
<?php // I will assume the rest of the url has already been stripped away $url = '/fruits/tropical/bananas/'; // lets just make sure we don't have any leading or trailing / $url = trim($url, '/'); // now let's split the remaining string based on the / $aUrl = explode('/', $url); /** * Now let's build the query to retrieve this */ // this array stores the values to be bound to the query at the end $aParams = array(); $field_list = 'SELECT p1.* '; $tables = 'FROM pages p1 '; $where = "WHERE p1.parent_id IS NULL AND p1.slug = ? "; // this array stores the values to be bound to the query at the end $aParams[] = $aUrl[0]; // if we have more than one element in our array we need to add to the query $count = count($aUrl); for ($i = 1; $i < $count; $i++) { // add another table to our query $table_alias = 'p' . ($i + 1); $prev_table_alias = 'p' . $i; $tables .= "INNER JOIN pages $table_alias ON {$prev_table_alias}.id = {$table_alias}.parent_id "; // add to where clause $where .= "AND {$table_alias}.slug = ? "; $aParams[] = $aUrl[$i]; // overwrite the content of $field_list each time so we // only retrieve the data for the actual page requested $field_list = "SELECT {$table_alias}.* "; } $sql = $field_list . $tables . $where; $result = $this->db->query($sql, $aParams);
Если страница существует только на одном URL-адресе, самым простым способом решения проблемы будет сохранение хэша полного URL-адреса в индексированном поле его собственного:
SELECT * FROM table WHERE page = MD5('http://localhost/cms/fruits/tropical/bananas/')
Хотя, если вы собираетесь идти по иерархическому маршруту, вы можете найти следующее полезное: http://mikehillyer.com/articles/managing-hierarchical-data-in-mysql/
Мне особенно нравится представление Билла Карвина ( https://stackoverflow.com/users/20860/bill-karwin ) по иерархиям:
http://en.slideshare.net/billkarwin/models-for-hierarchical-data
Рекурсивно захватить все данные на основе родительского идентификатора
Вы должны взглянуть на его решение с закрывающим столом. Я нашел его особенно полезным. Это может быть то, что вам нужно.
Вы отметили этот вопрос как CodeIgniter, так что это ответ на этот вопрос.
Вы можете использовать его возможности маршрутизации для принудительного URL-адреса, но обрабатывать запрос требуемым образом.
То, что у вас есть, это что-то вроде:
$route['cms/fruit/(:any)'] = 'fruit/$1'; $route['cms/fruit/(:any)/(:any)'] = 'fruit/$1/$2';
Первая строка переадресует все URL-адреса, начинающиеся с cms / fruit, в контроллер фруктов и передают тип фруктов в качестве первой переменной (возможно, имя фрукта также как вторая переменная). Вторая строка – это откат на случай, если он не позаботится о названии фрукта.
Объедините это с базовым путем в конфиге, и вы также можете автоматически установить «cms» в URL-адрес, если это то, что всегда должно быть в URL-адресе.
Вы используете codeigniter для этой разработки? Этот ответ ниже основан на структуре веб-приложений от codeigniter. Так вот,
Проблема в том, что я хочу, чтобы страница
[root]->fruits->tropical->bananas
была доступна только из этого URL:http://localhost/cms/fruits/tropical/bananas/
.
Итак, создайте функцию в контроллере с фруктовыми именами функций и двумя параметрами? Например
class Cms extends CI_Controller { ... ... ... public function __construct() { $this->load->model('cms_model'); } public function fruits($tropical, $bananas) { $string = $this->cms_model->getPage($tropical, $bananas); // load the view you want. $this->load->view(''); } ... ... ... }
До сих пор я пришел к выводу, что таблица cms имеет родительское поле, указывающее на его родительский элемент. Возникает вопрос: как разобрать адрес uri и выбрать строку из БД с минимальным количеством запросов / эффективно?
Table cms: Id Slug ParentId Table cms_parent: Id
Опишем приведенную выше таблицу из двух примеров: таблицу cms и родительскую таблицу cms. Вы не указали точно в своем вопросе о том, что ваш запрос хочет или результат запроса возвращается. Итак, ниже я догадываюсь, основываясь на вашем описании вопроса, а именно: запросите две таблицы, объединив их с помощью общего ключа, а затем примените условие где.
// select * from cms t1 join cms_parent t2 on t1.ParentId = t2.Id where t1.Id = '' and t2.ParentId = 'level1'; public function getPage($level0, $level1) { $this->db->select('*'); $this->db->from('cms'); $this->db->join('cms_parent', 'cms.ParentId = cms_parent.Id'); $this->db->where('cms.Id', $level0); $this->db->where('cms.ParentId', $level1); $query = $this->db->get(); // return one row from database. return $query->row(); }