Pull to refresh

Программируем стартап Веб 2.0 на PHP

Reading time18 min
Views4.3K
Программируем стартап Веб 2.0 на PHP
Итак, вы воодушевлены идеей стартапа Веб 2.0. Вы полагаете, что придумали что-то оригинальное и свежее. Вам видится эффектная реализация вашей идеи. Вы верите, что ваш проект произведет революцию на рынке. Если именно такие мысли занимают вас, самое время заняться бизнес-планом. Планирование бизнеса – это отдельная дисциплина и об этом можно найти множество литературы. Впрочем, если вы не имеете опыта составления бизнес-планов, лучше прибегнуть к помощи профессионалов. Чем хуже спрогнозирован бизнес, тем выше риски его краха.

Однако допустим, что вы располагаете привлекательным бизнес-планом, вы ожидаете самоокупаемость проекта уже через 2 года от момента его старта, и предвкушаете ежегодный 50% рост его рекламного потенциала. В MS-Visio подготовлены многообещающие схемы пользовательского интерфейса проекта. И даже более того, ваш дизайнер, вооружившись модным руководством, в рекордные сроки обозначил графический вид проекта. Так, что сверстанные в лучших традициях безтабличной верстки типовые страницы пользовательского интерфейса дожидаются своего звездного часа в вашей локальной папке проекта.

Схема проекта


Я не могу знать, в чем суть вашего стартапа, в чем его особенность. Но для того чтобы отталкиваться от какого-либо практического примера, давайте рассмотрим упрощенный вариант всеми любимого коллективного блога habrahabr.ru. Очевидно, что едва ли вы планируете повторить известный проект. Вы можете пойти по проверенному пути и разработать фотоблог в стиле flicr.com, социальную сеть a-la facebook.com, социальные закладки на манер ma.gnolia.com или социальные новости в традициях digg.com. Возможно, вы выберите собственную извилистую тропу на пути к коммерческому успеху. В любом случае вы столкнетесь с общими для проектов Веб 2.0 подходами, такие как комментирование, рейтингование, тегирование, всплывающие контекстные подсказки (tooltips) и т.д. А что касается ленты статей или, скажем, пользовательской панели – эти решения справедливы фактически для любого проекта.

Схема

Итак, в нашем примере имеется всего несколько информационных страниц («О сайте», «Помощь»), лента статей, пользовательская панель («Регистрация», «Авторизация»), облако тегов, связанное с лентой статей и лента последних комментариев.

Предположим, нам хотелось бы, чтобы итерации с формами (регистрация, авторизация, добавление комментария) по возможности не требовали перегрузки веб-страницы. Системные сообщения появлялись в заданном оформлении и поддерживали принцип «перетянул и оставил» (Drag&Drop). При выполнении процессов система должна сообщать о том, чем и как долго она занята.

Полагаю, теперь задача ясна, осталось подумать о ее реализации.

Выбор платформы


Просто взять и начать программировать – идея не самая лучшая. Нужны гарантии того, что на определенном этапе не придется начинать все с начала, что растущая посещаемость не будет проблемой, что «бесконечная бета» не превратиться в «бесконечную альфу» и что запросы пользователей о новых виджетах или необходимость «отрытого API» не станут для вас камнем преткновения. Хорошо бы положиться на чужой опыт, на проверенный подход. Решением здесь может быть использования одного из популярных фреймворков: Zend Framework, Prado, CakePHP, Symphony Project, Seagull Framework, WACT, PHP on TRAX, ZooP Framework, eZ Components или же CodeIgniter.

Каждый из них имеет свои сильные стороны. К примеру, производительность Zend Framework — вполне закономерное преимущество для решения от разработчиков PHP. Prado – отличает похожая на XAML модель декларирования пользовательских интерфейсов. Судя по всему, наибольшей гибкостью и масштабируемостью славится CakePHP. Впрочем, у всех фреймворков есть общие свойства. Все Фреймворки позволяют использовать различные СУБД, не требуя при этом изменения кода. Все из них поддерживают PHP5 (но не все при этом способны работать на PHP4). Во всех из их содержатся компоненты валидации данных. И почти все построены по модели MVC (Модель-Вид-Контроллер). Если у вас уже есть опыт работы с одним из этих Фреймворков, я не стану удерживать вас от использования любимой платформы. Однако если вы сейчас рассматриваете этот список, не зная с чего начинать, я спешу напомнить вам, что любое универсальное решение (а все эти фреймворки достаточно универсальны), не может сравниться в производительности с хорошо исполненным частным решением, предназначенным для решения только одной данной задачи. Что же, давайте попробуем создать собственною программную платформу для решения задач нашего стартапа. Итак, находим хостинг-план с PHP 5.2 и MySQL 4/5. Не забудьте удостовериться в том, что этот план, включает и расширение PHP PDO (PHP Data Objects). Использование этой библиотеки абстрактного доступа к БД позволит при необходимости легко переключиться на другую СУБД.

Компонентная модель


Теперь самое время определиться с компонентной моделью (с принципом размещения скриптов проекта). При удачной компонентной модели вы всегда быстро найдете требуемые скрипты, вам будет легко ориентироваться в системе в ходе отладки, у вас не будет проблем с развитием проекта.

Итак, в корневой папке проекта будет расположен index.php, который должен принимать все запросы проекта, подключать требуемые библиотеки и передавать управление соответствующим скриптам. Папка app будет содержать скрипты, обеспечивающие работоспособность различных веб-страниц проекта. Скрипты будут распределены по папкам controllers, models, views. Это та самая модель MVC. Для любой веб-страницы система найдет соответствующий скрипт модели и примет в нем все необходимые для данного интерфейса данные. Затем будет вызван соответствующий веб-странице скрипт вида, который оформит эти данные для вывода. Если веб-странице были переданы данные в POST или GET, прежде скриптов модели и вида, будет вызван скрипт контроллера, который произведет с потупившими данными требуемые действия. Помимо папок MVC, я предлагаю также завести папку ajax_controllers, где будут расположены контроллеры для асинхронных Java Script запросов (AJAX).

Компонентная модель

Возвращаемся в корневую папку и создаем классический набор папок для оформления проекта и скриптов JS (css/images/js). Создаем паку config, где будет храниться файл config.inc.php. В нем мы определим интерфейс и данные для доступа к БД, константы проекта HTTP_PATH и ROOT_PATH, константы с именами таблиц БД и прочие конфигурационные данные. Папка libs, будет содержать программные библиотеки. Загруженные пользователями файлы будут направлены в папку usercontent. В папке vendors будут помещены сторонние библиотеки и решения, такие как FCKEditor, LastRSS, MediaPlayers, YUI.

Как эта схема работает на практике? Давайте рассмотрим пример. Пользователь запросил веб-страницу нашсайт/blog.

Находящийся в корневой папке файл .htaccess перенаправил запрос в index.php

.htaccess

DirectoryIndex index.php
ErrorDocument 404 /404/
Options +Followsymlinks

<IfModule mod_rewrite.c>
	RewriteEngine on
	RewriteCond %{REQUEST_FILENAME} !-f 
	RewriteCond %{REQUEST_FILENAME} !-d
	RewriteRule ^(.*)$ index.php?%{QUERY_STRING} [L]
</IfModule>


Index.php считывает конфигурацию из config.php, включает небольшой набор общеупотребительных функций, таких как отладочная функция toLog(). Далее включается набор паттернов (patterns.inc.php) и производится инициализация среды (init.inc.php). В простейшем случае в ходе инициализации анализируется строка запроса $_SERVER['REQUEST_URI'] и исходя из ее содержания назначаются переменные $CTRLPATH, $RECORD_ID и $APPPATH. Каким образом? В случае запроса нашсайт/controller/адрес — $CTRLPATH принимает значение «адрес». В случае запроса нашсайт/адрес/000023 (маска регулярного выражения /\d{7}/) — $RECORD_ID принимает значение 23. $APPPATH –всегда принимает значение «адрес».

index.php

<?
if (preg_match("/\.(gif|jpg|bmp|js|css)\/?$/is", $_SERVER['REQUEST_URI'])) exit;
include(ROOT_PATH."config/config.inc.php");
include(ROOT_PATH."app/basics.inc.php");
include(ROOT_PATH."app/patterns.inc.php");
include(ROOT_PATH."app/init.inc.php");

$db = Lib::factory('db');
$db->applyAuthorization();

// Получить контроллер
if($CTRLPATH) {
	// Если есть кеш-образ ответа
	if(file_exists(ROOT_PATH."cache/".md5("ajax_controllers{$CTRLPATH}").".tmp")) 
		$OUT = file_get_contents(ROOT_PATH."cache/". 
		md5("ajax_controllers{$CTRLPATH}").".tmp"); 
	else { 
		$ctrl = Lib::factory('controller');
		include(ROOT_PATH. "app/ajax_controllers{$CTRLPATH}index.inc.php");
	}
} else {
	// Если есть кеш-образ страницы, показать его 
	if(file_exists(ROOT_PATH."cache/".md5($APPPATH).".tmp")) 
	$OUT = file_get_contents(ROOT_PATH."cache/".md5($APPPATH).".tmp"); 
else {
	// Получить веб-страницу
	$InterfaceScript = ($RECORD_ID?"record":"index").".inc.php";
	include(ROOT_PATH."app/controllers/common.inc.php");
	if(file_exists(ROOT_PATH."app/controllers{$APPPATH}". $InterfaceScript)) 
		include(ROOT_PATH."app/controllers{$APPPATH}". $InterfaceScript);
	include(ROOT_PATH."app/models/common.inc.php");
	if(file_exists(ROOT_PATH."app/models{$APPPATH}".$InterfaceScript)) 
		include(ROOT_PATH."app/models{$APPPATH}".$InterfaceScript);
	if(file_exists(ROOT_PATH."app/views{$APPPATH}".$InterfaceScript)) 
		include(ROOT_PATH."app/views{$APPPATH}".$InterfaceScript);
	else 
		include(ROOT_PATH."app/views/_404/index.inc.php");
}
}

header("Content-type: text/html; charset=UTF-8");
print $OUT;
if(isset($_GET["createcache"]))
	file_put_contents(ROOT_PATH."cache/".md5($APPPATH).".tmp", $OUT);
?>


app/patterns.inc.php

<?
class Lib {
	// The parameterized factory method
	public static function factory($type) {
		if (include_once ROOT_PATH.'libs/' . $type . '.lib.php') {
			$classname = $type;
			return new $classname;
		} else {
			throw new Exception ('Driver not found');
		}
	}
}
?>


Далее система устанавливает соединение с БД и проверяет, не авторизован ли пользователь. Так как была запрошена веб-страница, а не запись списка будут последовательно опрошены

app/controllers/blog/index.inc.php
app/models/blog/index.inc.php
app/views/blog/index.inc.php

POST и GET не содержат данных и соответственно скрипт контроллера будет пропущен. В скрипте модели app/models/blog/index.inc.php будут приняты данные для списка статей, а в скрипте вида app/views/blog/index.inc.php эти данные будут оформлены для вывода.

app/models/blog/index.inc.php

<?
$CONTENT["WINDOW_TITLE"] = "Стартап: блоги";
$NAVIGATION["limit"] = 5;
$get = Lib::factory('get');
	$DATA["MEDIALIST"] = $get->articleList();
	$DATA["PAGINATION"] = $get->pagination();
?>


app/views/blog/index.inc.php

<?
$CONTENT["BODY"] = '';
	foreach($DATA["MEDIALIST"] as $row) {
	$rec_url = HTTP_PATH.$MEDIATYPES[$row["media_type"]]["var"].
	 '/'.getIDUrl($row["media_id"]).'/';
	$CONTENT["BODY"] .= '
	<div class="media_line">
	<div class="info">Добавил(а) '.$row["fullname"].', просмотрено '.
		(int)$row["visited"].' раз</div>
	<div class="text">'.$row["description"].'</div>
	<div class="footer">'.($row["cache_commentnumber"]?'<a href="'.
		$rec_url.'">'.$row["cache_commentnumber"].
		' комментариев</a>':'<a href="'.
		$rec_url.'">Добавить комментарий</a>').'
	</div>
	</div> ';
}

include(INCLUDEPATH."header.inc.php");
$OUT .= '
	<div class=”left_col”>
	'. $CONTENT["BODY"].$CONTENT["PAGINATION"].'
	</div>
	<div class=”right_col”>
	<div id=”UserPanel”></div>
	<div id=”TagCloud”></div>
	<div id=”LastestComments”></div>
	<script type="text/javascript">
	showBlock("UserPanel");
	showBlock("TagCloud");
	showBlock("LastestComments");
	</script>
	</div>
';
include(INCLUDEPATH."footer.inc.php");
?>


Возможно вы обратили внимание на вызов класса get для получения списка статей. Благодаря паттерну factory, мы можем загружать в память только те библиотеки, которые мы используем в актуальных задачах. Но предлагаю пойти дальше и спроектировать библиотеки таким образом, чтобы для наиболее часто запрашиваемых веб-страниц мы ограничились минимальным расходом памяти. Давайте поместим все методы запросов данных, которые будут востребованы в каждом интерфейсе в класс get. Прочие методы можно распределить по классам в соответствии с логикой нашего проекта. Они будут получены фабрикой лишь по мере необходимости. Например, когда требуется сохранить содержание пользовательского комментария.

Как видно из скрипта вида в данном случае будет отображена страница, содержащая список статей в левой колонке. В правой колонке ожидаются пользовательская панель, облако тегов, последние комментарии. В скрипте вида пока мы можем видеть только контейнеры этих панелей. Однако вызовы функции showBlock примут и вставят в эти контейнеры содержание панелей.

Итак, мы получили список статей. Теперь требуется рассмотреть случай запроса пользователем отдельной статьи из этого списка. В этом случае в ходе инициализации среды будет определена переменная $RECORD_ID. Система опросит универсальную модель для любой из записей списка blog. Модель будет найдена по адресу app/models/blog/record.inc.php. $RECORD_ID послужит параметром для запроса данных статьи.

$get->article($RECORD_ID);


Скрипт вида система будет искать по адресу app/views/blog/record.inc.php.

По аналогии с веб-страницей списка статей, когда пользователь запросит информационную страницу (например, нашсайт/about) в скрипте модели будут получены данные страницы (app/models/about/index.inc.php), в скрипте вида (app/views/about/index.inc.php) будет задано оформление.

Обогащаем пользовательский интерфейс


По условиям задачи система должна при необходимости показывать сообщения в стилизированных перемещаемых окнах, сообщать о состоянии процессов. Наиболее простой путь – использовать компонент panel из библиотеки пользовательских интерфейсов от Yahoo (http://developer.yahoo.com/yui/container/panel/). Однако если вы желаете избавить пользователей вашего проекта от необходимости ожидания загрузки дополнительного JS-скрипта размером порядка 100Кб, можно попробовать сделать собственную библиотеку. Для системных сообщений вам потребуются две функции showSystemMessage() и hideSystemMessage(). Первая будет показывать скрытый слой окна сообщения ( document.getElementById («window_id»).style.display=«block» ) и помещать туда переданное в функцию сообщение ( document.getElementById(«window_content_is»).innerHTML = message ). Вторая функция будет скрывать сообщение ( document.getElementById («window_id»).style.display=«none» ). Желательно также перед показом слоя программно позиционировать его в центре окна браузера.

Отлично, теперь мы можем показывать окно системного сообщения и скрывать его. Но как включить для него Drag&Drop? Достаточно просто указать в слое окна диспетчеры событий для операций с мышью:

<div id=”window_id” onmousedown=«windowMouseDown('window_id', event)» onmouseup=«windowMouseUp('window_id')»>… </div>

и добавить в JS-скрипты проекта обработку этих событий.

js/panels.js

// Быстрый доступ к объекту
function $(divName) { return document.getElementById(divName); }

// Определяем тип браузера
if(document.implementation && document.implementation.createDocument) var isMozilla=true;
else var isMozilla=false;

// Захватить объект для перемещения и его координаты
function windowMouseDown(divNamePref, ev) { 
	if (isMozilla) { event=ev; }
	currentWindowDivNamePref = divNamePref; 
	// Save offset
	currentWindow[divNamePref] = {
	"x" : event.clientX + document.body.scrollLeft - 
		$(divNamePref).style.left.replace("px",""),
	"y" : event.clientY + document.body.scrollTop - 
		$(divNamePref).style.top.replace("px","")
	};
}
// Двигать объект
function windowMouseMove(ev) {
	if(!currentWindowDivNamePref) return false;
	if(!currentWindow[currentWindowDivNamePref]) return false;
	if (isMozilla) { event=ev; }
	$(currentWindowDivNamePref).style.left = (event.clientX + 
		document.body.scrollLeft - currentWindow[currentWindowDivNamePref].x) + "px";
	$(currentWindowDivNamePref).style.top = (event.clientY + 
		document.body.scrollTop - currentWindow[currentWindowDivNamePref].y) + "px";
	return false;
}

// Отпустить объект
function windowMouseUp(divNamePref) { 
	currentWindow[divNamePref] = null; 
	currentWindowDivNamePref = false; 
} 

if (isMozilla) { document.captureEvents(Event.MOUSEMOVE); }
document.onmousemove = windowMouseMove;


В случае окна состояния процесса ниже слоя окна добавляется еще полупрозрачный слой, залитый серым цветом и закрывающий собой содержание всего окна браузера. Таким образом мы получаем эффект временного «замораживания» пользовательского интерфейса. В сообщении окна можно добавить динамичное изображение, визуализирующее процесс. Как правило, в коде слоя окна системного сообщения размещается кнопка «Закрыть». В случае окна состояния процесса этого не требуется.

Теперь рассмотрим практическую задачу, которая потребует, описанных функций управления системными окнами. Нам необходимо реализовать в проекте пользовательскую панель. Это компонент, позволяющий посетителям проекта либо зарегистрироваться, либо авторизоваться. Т.е. когда пользователь заполнил форму для регистрации, система должна отправить его данные на сервер, проанализировать их и либо сообщить об ошибке заполнения (скажем, если был введен некорректно код подтверждения Captcha), либо зарегистрировать пользователя и, опять же, сообщить о результате. Сообщить о чем-либо мы теперь можем с помощью окна системного сообщения. Как вы, должно быть, помните уже, для того чтобы система просто отображала пользовательскую панель нам потребуется JS-запрос на сервер (функция showBlock()). Для того, чтобы проверить введенные пользователем данные не перегружая веб-страницу опять же не обойтись без AJAX. Таким образом, нам нужен набор JS-функций для отправки асинхронных запросов в контроллер на сервере и получения ответов контроллера. В данном случае опять же можно использовать собственное решение, а можно положиться на опыт популярных open source библиотек. Давайте рассмотрим вариант использования компонента Connection Manager из YUI (http://developer.yahoo.com/yui/connection/). Для его использования придется вызывать в коде страниц проекта два скрипта yahoo-min.js и connection-min.js.

Давайте посмотрим, каким образом работает функция showBlock, отображающая вспомогательные компоненты веб-страницы.

js/common.js

// Функции AJAX

// Быстрый доступ к объекту
function $(divName) { return document.getElementById(divName); }

// Послать запрос контроллеру
function showBlock(BlockID) {
	YAHOO.util.Connect.asyncRequest('POST', "http://нашсайт/controller/"+ 
		BlockID.toLowerCase() +"/", 
	callback, "ctrl_action=getComponent");
}

// Определить поведение системы при получении ответа 
var callback =
{
	success:CtrlRespond,
	failure:commonHandleFailure,
	argument:['foo','bar']
};

// Неудачный ответ
var commonHandleFailure = function(o){ 
	if(o.responseText !== undefined){
	showSystemMessage("Connection Error");
}
};

// Анализ ответа контроллера
var CtrlRespond = function(obj){
if(obj.responseText == undefined) return false;
if(obj.responseText.substr(0,1)=="{") {
	var respondStructure = eval( '(' + obj.responseText + ')' ); 
	// Контроллер просит показать ошибку
	if( respondStructure.ErrorMsg ) return showSystemMessage("ОШИБКА:" + 
		respondStructure.ErrorMsg); 
	// Контроллер просит выполнить соответствующее коду действие
	if( respondStructure.ActionCode==1 ) { $( respondStructure.ID ).innerHTML= 
	respondStructure.Body; return true; 
	}
	// Контроллер просит показать сообщение
	if( respondStructure.Body ) showSystemMessage(respondStructure.Body); 
	} else alert(obj.responseText); // Отладка в случае ответа 
	// с некорректной для JSON структурой
	};


app/ajax_controllers/userpanel/index.inc.php


<?
// Контроллер для пользовательской панели
class RD extends controller {
	private $user;
	
	function __construct() {
	$this->user = Lib::factory('user');
	}

	function getComponent() {
		$this->ID = "UserPanel";
		$this->ActionCode = 1;
		$this->Body = " ..Содержание панели .. ";
	}
}

$rd = new RD();

if(isset($_POST["ctrl_action"])) {
	call_user_method($_POST["ctrl_action"], $rd);
}
$rd->respond();
?>


libs/controller.lib.php


<?
// Исходный класс контроллера
class controller {
public $ActionCode=1;
public $ErrorMsg="";
public $Body="";
public $ID="";


function respond($message="", $errormsg="") {
	if($message) $this->Body = $message;
	if($errormsg) $this->ErrorMsg = $errormsg;
		$out = '{
		"ActionCode": "'.$this->ActionCode.'",
		"ID": "'.$this->ID.'",
		"ErrorMsg" : "'.($this->ErrorMsg?addslashes(preg_replace("/[\r\n]/",
		 	"", $this->ErrorMsg)):"").'",
		"Body" : "'.($this->Body?addslashes(preg_replace("/[\r\n]/", 
			"", $this->Body)):"").'"
		}';
	header("Content-type: text/html; charset=UTF-8");
	print $out; 
	exit;
	}
} 
?>


Когда отработала инструкция в HTML веб-страницы showBlock(«UserPanel») происходит запрос к контроллеру app/ajax_controllers/userpanel/index.inc.php. В параметре запроса ctrl_action указывается запрашиваемый метод. Контроллер возвращает в Java Script JSON-структуру с переменными ID, ActionCode, Body. Функция JS CtrlRespond() анализирует полученные переменные. Для ActionCode==1, как это в нашем случае, она вставляет содержание, полученное в BODY в слой с идентификатором ID.

Коль скоро мы получили пользовательскую панель, мы можем написать функцию для передачи данных контроллеру при регистрации посетителя проекта.

js/common.js


// Послать данные в контроллер
function registerUser(obj) {
	if(obj.login.value=='' || obj.password.value=='' || 
	obj.email.value=='' || obj.fullname.value=='' || obj.gencode.value==''
	) showSystemMessage('Следует заполнить все поля формы');
	else YAHOO.util.Connect.asyncRequest('POST', "http://нашсайт/controller/userpanel/", 
		callback, "ctrl_action=createUser&login="+
		obj.login.value+ "&password="+obj.password.value+
		"&email="+obj.email.value+ "&fullname="+
		obj.fullname.value+"&gencode="+obj.gencode.value);
}

…

// Анализ ответа контроллера
var CtrlRespond = function(obj){
…
	if( respondStructure.ActionCode==2 ) return showSystemMessage(respondStructure.Body); 
…
};


app/ajax_controllers/userpanel/index.inc.php


<?
// Контроллер для пользовательской панели
class RD extends controller {
…
	function createUser() {
	if(!$_POST) { $this->ErrorMsg = "Не заполнены необходимые поля"; return false; }
	if(!$this->user->add($data)) { $this->ErrorMsg = 
		"Ошибка при добавлении пользователя"; return false; }
	$this->ActionCode = 2;
	$this->Body = "Пользователь создан успешно";
	}
…
}
?>


Форма регистрации пользователя содержит кнопку <input type=«button» value=«Присоединиться» onclick=«registerUser(this) » />. При клике по ней в контроллер app/ajax_controllers/userpanel/index.inc.php передаются данные из формы и запрашивается метод createUser() для их обработки. В ходе выполнения метода в структуру ответа контроллера могут быть направлены сообщение об ошибке ErrorMsg, код требования о выполнении команды на клиентской стороне ActionCode или текст сообщения Body. В данном случае при успешном создании пользователя отобразится соответствующее сообщение, а в случае ошибки сообщение о ней.

Аналогичным образом мы можем написать функции для запросов проверки наличия введенного логина или email в БД. Они будут запрашивать методы класса контроллера app/ajax_controllers/userpanel/index.inc.php checkLogin и checkEmail соответственно. Сами методы могут выполнять проверку и сообщать о результате в переменной BODY. По схожему принципу можно организовать отображение формы авторизации и реакцию на события в ней. Впрочем, это касается любых компонентов пользовательского интерфейса проекта, требующих AJAX.

При отображении облака тегов вам может пригодиться следующая функция

Облако тегов

<?
function cmp_tag($a, $b) {
	if($a["tag"] == $b["tag"]) return 0;
	return strcmp($a["tag"] , $b["tag"]);
}
function getClouds() {
	global $db;
	$lines = array();
	$sizes = array( "x-small", "small", "medium", "large", "x-large" );
	$sql = "SELECT * FROM ".TAGCLOUDINDEXTABLE." LIMIT 0,20";
	$sth = $db->prepare($sql);
	$sth->execute();
	$result = $sth->fetchAll(PDO::FETCH_ASSOC);
	if(!$result) return false;
	$indexes = array();
	$tags = array();
	foreach ($result as $line) {
		$tags[trim($line["tag"])] = $line["tag_index"];
		$indexes[] = $line["tag_index"];
	} 
	$min = min($indexes);
	$max = max($indexes);
	$range = ($max-$min);
	foreach ($tags as $tag => $index) {
		$lines[$tag]["tag"] = $tag;
		$lines[$tag]["tag_index"] = $index;
		$lines[$tag]["size"] = $sizes[sprintf("%d", ($index-$min)/$range*4 )];
		$lines[$tag]["title"] = "Тег ".$tag." найден ".$index.' раз ';
	}
	usort ($lines, "cmp_tag");
	return $lines;
} 

?>


Для реализации всплывающих контекстных подсказок вы можете использовать тот же принцип, по которому были созданы окна системных сообщений, с той лишь разницей, что подсказки следует позиционировать непосредственно в позиции курсора мыши в момент клика. Функции позиционирования, отображения и кеширования подсказок вы можете взять в моей библиотеке Thesaurus (http://www.phpclasses.org/browse/package/3505.html)

Пример Tooltip

Оптимизация


Уверен, вы рассчитываете на высокую посещаемость разрабатываемого проекта. Значит, будет не лишним снизить нагрузку на сервер, настолько насколько это возможно.

Укажите планировщику (CRONTAB) вашего сервера задачу «раз в 30 минут» выполнять скрипт index.php c GET параметром createcache (/usr/bin/php -f/полныйадрес/index.php "&createcache =on"). Система будет создавать кеш образ для главной страницы каждые полчаса. Наибольшая частота обращений приходится обычно именно на главную страницу. В нашем случае проект будет возвращать уже заранее подготовленный HTML, пропуская относительно ресурсоемкие операции, обращение к БД и т.д. Впрочем, когда страница загрузится в браузере пользователя, Java Script произведет запрос вспомогательных компонентов (пользовательская панель и прочие). Ответы контроллера также можно кешировать. Следует добавить в контроллер анализ GET переменных:

if(isset($_GET["ctrl_action"])) {
	call_user_method($_GET["ctrl_action"], $rd);
}


Добавить в метод respond исходного класса условие создание кеш-образа ответа:

print $out; 
if(isset($_GET["createcache"]))
	file_put_contents(ROOT_PATH."cache/".md5("ajax_controllers{$CTRLPATH}").".tmp", $OUT);
exit;


Теперь при прямом обращении к контроллеру нашсайт/controller/tagcloud? ctrl_action=getComponent&createcache=on будет создан кеш его ответа.
Осталось назначить планировщику задачи запуска заданных скриптов с параметрами (/usr/bin/php -f/полныйадрес/index.php "& ctrl_action=getComponent&createcache=on&request_uri=/controller/tagcloud/") через заданные промежутки времени (скажем, обновлять облако тегов каждые 2 часа). Только не забудьте убедиться о проверке наличия в скрипте инициализации среды app/init.inc.php параметра GET request_uri. Если таковой имеется, система должна использовать его взамен $_SERVER['REQUEST_URI'].

Заключение


В это шальное время, когда проекты Веб 2.0 оцениваются в миллионы долларов даже в России, вполне объяснимо желание попробовать себя на этом рынке. Вывод проекта на рынок, его капитализация – это вопросы бизнеса, а не программирования. Однако чтобы заниматься этим бизнесом, требуется продукт. Данная статья не являет руководством по программированию проекта Веб 2.0. Статья содержит материал, который может направить вас, если вы ищите подходящий путь разработки проекта. Я хотел здесь также показать, что, приловчившись можно «оживить» типовой Веб 2.0 стартап в относительно небольшие сроки. Если у вас есть интересная оригинальная идея, то пусть трудности ее реализации не останавливают вас. Дерзайте и, кто знает, возможно, именно ваша идея окажется реально востребованной, возможно она принесет вам больше чем вы смеете надеется. В Интернете имеется достаточно историй ошеломительного успеха стартапов Веб 2.0, стратапов сделанных людьми, которые осмелились. Возможно, через время там появиться и ваша success story. Успехов!..

Оригинал статьи в PDF
Tags:
Hubs:
+81
Comments140

Articles

Change theme settings