<?xml version="1.0" encoding="utf-8"?><!-- generator="b2evolution/4.0.3" -->
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:admin="http://webns.net/mvcb/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:atom="http://www.w3.org/2005/Atom">
	<channel>
		<title>Веб-Блог.рф: для тех, кто живет в Интернете</title>
		<link>http://new.xpro.su/</link>
		<atom:link rel="self" type="application/rss+xml" href="http://new.xpro.su/?tempskin=_rss2" />
		<description></description>
		<language>ru-RU</language>
		<docs>http://blogs.law.harvard.edu/tech/rss</docs>
		<admin:generatorAgent rdf:resource="http://b2evolution.net/?v=4.0.3"/>
		<ttl>60</ttl>
				<item>
			<title>Новый способ ввода данных для смартфонов</title>
			<link>http://new.xpro.su/ideas/smart-input</link>
			<pubDate>Sun, 11 Dec 2011 19:58:00 +0000</pubDate>			<dc:creator>4X_Pro</dc:creator>
			<category domain="main">Идеи и размышления</category>			<guid isPermaLink="false">104@http://new.xpro.su/</guid>
						<description>&lt;div&gt;&lt;div&gt;&lt;a href=&quot;http://new.xpro.su/media/blogs/blog/smart.png?mtime=1323633380&quot;&gt;&lt;img alt=&quot;&quot; src=&quot;http://new.xpro.su/media/blogs/blog/./_evocache/smart.png/fit-320x320.png?mtime=1323633380&quot; width=&quot;257&quot; height=&quot;320&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;Возникла идея принципиально нового способа ввода данных в устройства с  сенсорным экраном, которая сочетала бы в себе достоинства экранной  клавиатуры, T9 и Swype. Идея такая: пользователь набирает слово целиком  непрерывным движением пальца, но при этом на экране отображается не вся  клавиатура в целом (как в Swype), а только те буквы, у которых наиболее  высокая вероятность оказаться следующими, и вариант &quot;Еще&quot; для случая,  когда ни одна из предложенных букв не подходит. Кроме того, для ввода  используется поверхность экрана целиком, а кнопки ввода рисуются  полупрозрачными, чтобы сквозь них было видно введенный текст.&lt;/p&gt;
&lt;p&gt;Рассмотрим процесс ввода данных по шагам.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Шаг 0&lt;/strong&gt;. После переключения в режим ввода на экране появляются  круглые кнопки с буквами, с которых в языке начинается больше всего  слов, и кнопка с вариантом &quot;Еще!&quot;, выделенная, например, цветом.  Количество кнопок выбирается так, чтобы их можно было разместить по  окружности вокруг центра экрана и при этом оставалось достаточно места,  чтобы при необходимости легко можно было провести палец между ними.  (Число с учетом кнопки &quot;Еще&quot; должно быть нечетным, скорее всего,  наиболее распространенными вариантами будут 5, 7 и 9 кнопок в  зависимости от размера экрана.) В углах экрана могут размещаться  треугольные кнопки для служебных действий, например, переключение  регистра, смена языка ввода, переключение в режим обычной клавиатуры,  завершение ввода.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Шаг 1&lt;/strong&gt;. Если буква, с которой начинается желаемое слово, есть  на экране, нажимаем на нее и переходим к шагу 2. Иначе жмем &quot;Еще&quot; и  переходим к шагу 4.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Шаг 2.&lt;/strong&gt; Выбранная буква считается введенной. Кнопка с ней  отъезжает в дальний угол экрана (так, чтобы остальные кнопки, которые  были видны на шаге 1, оказались за его пределами) и становится  перечеркнутой. В центре снова появляется кольцо из кнопок с буквами,  причем кольцо по сравнению с предыдущим повернуто на определенный угол  так, чтобы палец пользователя оказался между новыми кнопками, а не на  одной из них. Кроме того, на этот раз предлагаются те буквы, которые  статистически чаще всего встречаются после введенной, причем чем больше  вероятность буквы оказаться следующей, тем ближе она оказывается к  пальцу пользователя. В оставшихся трех углах экрана могут появляться  варианты завершения слова целиком, если системе удалось его распознать.&lt;/p&gt;
&lt;p&gt;Далее возможны варианты: &lt;br /&gt;а) Хотим ввести следующую букву, перемещаем палец на нее без отрыва от экрана, и далее шаг 2 повторяется уже для третьей буквы. &lt;br /&gt;б) Нужной буквы нет, выбираем вариат &quot;Еще&quot; и переходим к шагу 4. &lt;br /&gt;в)  На предыдущем шаге ошиблись и хотим стереть введенную букву, тогда  перемещаем палец в тот дальний угол, где она нарисована с  перечеркиванием, после чего происходит возврат к предыдущему шагу (при  этом круг с буквами предыдущего шага как бы &quot;въезжает&quot; из-за края  экрана). &lt;br /&gt;г) Если система предложила то слово, которое мы хотим ввести, в одном из углов экрана, то ведем палец туда. &lt;br /&gt;д) Если ввели (или выбрали из предложенных вариантов завершения) слово целиком, переходим к шагу 3.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Шаг 3&lt;/strong&gt;. После того, как введено слово целиком, отрываем палец  от экрана, после чего на экране появляется круг  с кнопками знаков  препинания и пробелом, где выбираем нужный однократным нажатием. После  этого возвращаемся к шагу 0 и вводим следующее слово.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Шаг 4&lt;/strong&gt;. После нажатия кнопки &quot;Еще&quot; она отъезжает в дальний угол  и становится перечеркнутой (как в шаге 1, но при этом никакой символ не  считается введенным), и появляется новый круг из букв,  у которых  вероятность оказаться на данной позиции меньше, чем у предыдущих, а так  же еще одна кнопка &quot;Еще&quot;, по которой будет новый набор букв с еще  меньшей вероятностью (и так пока не переберем весь алфавит). Если  вариант &quot;Еще&quot; выбран по ошибке, то возвращаемся по перечеркнуой кнопке в  углу аналогично шагу 2в.&lt;/p&gt;
&lt;p&gt;Таким образом, получаем достаточно простой и наглядный способ ввода.  Для снижения ошибок можно сделать небольшую (где-то 0.2 секунды)  задержку перед срабатыванием кнопки или определять замедление пальца или  сопоставлять площадь, которую занимает палец на сенсоре, с площадью  кнопки (хотя для этого потребуется низкоуровневая работа с сенсором).&lt;/p&gt;
&lt;p&gt;По сравнению с экранной клавиатурой он более удобен тем, что не нужно  целиться пальцем в одну из трех десятков близко расположенных маленьких  кнопок (особенно на маленьких экранах), а достаточно попасть в одну из  семи больших. По сравнению с T9 и Swype гораздо проще вводить те слова,  которые не предусмотрены в словаре, а так же слова, содержащие  спецсимволы или смешанный набор букв и цифр.&lt;/p&gt;
&lt;p&gt;На рисунке ниже показан пример ввода текста &quot;Привет, мир!&quot;.  Ярко-красным обозначен жест пользователя на данном этапе, более темным  -- предыдущие жесты. Примечание: поскольку это пример, буквы выбраны без  серьезного статистического исследования.&lt;/p&gt;&lt;div class=&quot;item_footer&quot;&gt;&lt;p&gt;&lt;small&gt;&lt;a href=&quot;http://new.xpro.su/ideas/smart-input&quot;&gt;Original post&lt;/a&gt; blogged on &lt;a href=&quot;http://b2evolution.net/&quot;&gt;b2evolution&lt;/a&gt;.&lt;/small&gt;&lt;/p&gt;&lt;/div&gt;</description>
			<content:encoded><![CDATA[<div><div><a href="http://new.xpro.su/media/blogs/blog/smart.png?mtime=1323633380"><img alt="" src="http://new.xpro.su/media/blogs/blog/./_evocache/smart.png/fit-320x320.png?mtime=1323633380" width="257" height="320" /></a></div></div><p>Возникла идея принципиально нового способа ввода данных в устройства с  сенсорным экраном, которая сочетала бы в себе достоинства экранной  клавиатуры, T9 и Swype. Идея такая: пользователь набирает слово целиком  непрерывным движением пальца, но при этом на экране отображается не вся  клавиатура в целом (как в Swype), а только те буквы, у которых наиболее  высокая вероятность оказаться следующими, и вариант "Еще" для случая,  когда ни одна из предложенных букв не подходит. Кроме того, для ввода  используется поверхность экрана целиком, а кнопки ввода рисуются  полупрозрачными, чтобы сквозь них было видно введенный текст.</p>
<p>Рассмотрим процесс ввода данных по шагам.</p>
<p><strong>Шаг 0</strong>. После переключения в режим ввода на экране появляются  круглые кнопки с буквами, с которых в языке начинается больше всего  слов, и кнопка с вариантом "Еще!", выделенная, например, цветом.  Количество кнопок выбирается так, чтобы их можно было разместить по  окружности вокруг центра экрана и при этом оставалось достаточно места,  чтобы при необходимости легко можно было провести палец между ними.  (Число с учетом кнопки "Еще" должно быть нечетным, скорее всего,  наиболее распространенными вариантами будут 5, 7 и 9 кнопок в  зависимости от размера экрана.) В углах экрана могут размещаться  треугольные кнопки для служебных действий, например, переключение  регистра, смена языка ввода, переключение в режим обычной клавиатуры,  завершение ввода.</p>
<p><strong>Шаг 1</strong>. Если буква, с которой начинается желаемое слово, есть  на экране, нажимаем на нее и переходим к шагу 2. Иначе жмем "Еще" и  переходим к шагу 4.</p>
<p><strong>Шаг 2.</strong> Выбранная буква считается введенной. Кнопка с ней  отъезжает в дальний угол экрана (так, чтобы остальные кнопки, которые  были видны на шаге 1, оказались за его пределами) и становится  перечеркнутой. В центре снова появляется кольцо из кнопок с буквами,  причем кольцо по сравнению с предыдущим повернуто на определенный угол  так, чтобы палец пользователя оказался между новыми кнопками, а не на  одной из них. Кроме того, на этот раз предлагаются те буквы, которые  статистически чаще всего встречаются после введенной, причем чем больше  вероятность буквы оказаться следующей, тем ближе она оказывается к  пальцу пользователя. В оставшихся трех углах экрана могут появляться  варианты завершения слова целиком, если системе удалось его распознать.</p>
<p>Далее возможны варианты: <br />а) Хотим ввести следующую букву, перемещаем палец на нее без отрыва от экрана, и далее шаг 2 повторяется уже для третьей буквы. <br />б) Нужной буквы нет, выбираем вариат "Еще" и переходим к шагу 4. <br />в)  На предыдущем шаге ошиблись и хотим стереть введенную букву, тогда  перемещаем палец в тот дальний угол, где она нарисована с  перечеркиванием, после чего происходит возврат к предыдущему шагу (при  этом круг с буквами предыдущего шага как бы "въезжает" из-за края  экрана). <br />г) Если система предложила то слово, которое мы хотим ввести, в одном из углов экрана, то ведем палец туда. <br />д) Если ввели (или выбрали из предложенных вариантов завершения) слово целиком, переходим к шагу 3.</p>
<p><strong>Шаг 3</strong>. После того, как введено слово целиком, отрываем палец  от экрана, после чего на экране появляется круг  с кнопками знаков  препинания и пробелом, где выбираем нужный однократным нажатием. После  этого возвращаемся к шагу 0 и вводим следующее слово.</p>
<p><strong>Шаг 4</strong>. После нажатия кнопки "Еще" она отъезжает в дальний угол  и становится перечеркнутой (как в шаге 1, но при этом никакой символ не  считается введенным), и появляется новый круг из букв,  у которых  вероятность оказаться на данной позиции меньше, чем у предыдущих, а так  же еще одна кнопка "Еще", по которой будет новый набор букв с еще  меньшей вероятностью (и так пока не переберем весь алфавит). Если  вариант "Еще" выбран по ошибке, то возвращаемся по перечеркнуой кнопке в  углу аналогично шагу 2в.</p>
<p>Таким образом, получаем достаточно простой и наглядный способ ввода.  Для снижения ошибок можно сделать небольшую (где-то 0.2 секунды)  задержку перед срабатыванием кнопки или определять замедление пальца или  сопоставлять площадь, которую занимает палец на сенсоре, с площадью  кнопки (хотя для этого потребуется низкоуровневая работа с сенсором).</p>
<p>По сравнению с экранной клавиатурой он более удобен тем, что не нужно  целиться пальцем в одну из трех десятков близко расположенных маленьких  кнопок (особенно на маленьких экранах), а достаточно попасть в одну из  семи больших. По сравнению с T9 и Swype гораздо проще вводить те слова,  которые не предусмотрены в словаре, а так же слова, содержащие  спецсимволы или смешанный набор букв и цифр.</p>
<p>На рисунке ниже показан пример ввода текста "Привет, мир!".  Ярко-красным обозначен жест пользователя на данном этапе, более темным  -- предыдущие жесты. Примечание: поскольку это пример, буквы выбраны без  серьезного статистического исследования.</p><div class="item_footer"><p><small><a href="http://new.xpro.su/ideas/smart-input">Original post</a> blogged on <a href="http://b2evolution.net/">b2evolution</a>.</small></p></div>]]></content:encoded>
								<comments>http://new.xpro.su/ideas/smart-input#comments</comments>
			<wfw:commentRss>http://new.xpro.su/?tempskin=_rss2&#38;disp=comments&#38;p=104</wfw:commentRss>
		</item>
				<item>
			<title>Сканирование черно-белых рисунков</title>
			<link>http://new.xpro.su/raznoe/scan</link>
			<pubDate>Fri, 09 Dec 2011 16:10:00 +0000</pubDate>			<dc:creator>4X_Pro</dc:creator>
			<category domain="main">Разное</category>
<category domain="alt">Linux</category>			<guid isPermaLink="false">103@http://new.xpro.su/</guid>
						<description>&lt;p&gt;Иногда сталкиваюсь с необходимостью сканировать различные черно-белые рисунки, графики, диаграммы под Linux. При этом если сканировать в greyscale, изображения часто получаются некачественными: либо просвечивает обратная сторона листа, либо фон получается серым и грязным и неприятно выделяется при вставке рисунка в документ. Если же сделать изображение двухцветным, то линии становятся зазубренными.&lt;/p&gt;
&lt;p&gt;Недавно нашел способ, как с этим бороться: после сканирования в greyscale нужно сильно повысить контрастность изображения, либо в программе sane, либо после сканирования в каком-нибудь графическом редакторе, а так же выставить небольшую гамма-коррекцию. В частности, я использовал такие параметры: в sane гамма 1.30, затем открывал изображение в GIMP и выставлял там контрастность на +30—+50. В результате получалось качественное изображение с чисто белым фоном и гладкими линиями.&lt;/p&gt;&lt;div class=&quot;item_footer&quot;&gt;&lt;p&gt;&lt;small&gt;&lt;a href=&quot;http://new.xpro.su/raznoe/scan&quot;&gt;Original post&lt;/a&gt; blogged on &lt;a href=&quot;http://b2evolution.net/&quot;&gt;b2evolution&lt;/a&gt;.&lt;/small&gt;&lt;/p&gt;&lt;/div&gt;</description>
			<content:encoded><![CDATA[<p>Иногда сталкиваюсь с необходимостью сканировать различные черно-белые рисунки, графики, диаграммы под Linux. При этом если сканировать в greyscale, изображения часто получаются некачественными: либо просвечивает обратная сторона листа, либо фон получается серым и грязным и неприятно выделяется при вставке рисунка в документ. Если же сделать изображение двухцветным, то линии становятся зазубренными.</p>
<p>Недавно нашел способ, как с этим бороться: после сканирования в greyscale нужно сильно повысить контрастность изображения, либо в программе sane, либо после сканирования в каком-нибудь графическом редакторе, а так же выставить небольшую гамма-коррекцию. В частности, я использовал такие параметры: в sane гамма 1.30, затем открывал изображение в GIMP и выставлял там контрастность на +30—+50. В результате получалось качественное изображение с чисто белым фоном и гладкими линиями.</p><div class="item_footer"><p><small><a href="http://new.xpro.su/raznoe/scan">Original post</a> blogged on <a href="http://b2evolution.net/">b2evolution</a>.</small></p></div>]]></content:encoded>
								<comments>http://new.xpro.su/raznoe/scan#comments</comments>
			<wfw:commentRss>http://new.xpro.su/?tempskin=_rss2&#38;disp=comments&#38;p=103</wfw:commentRss>
		</item>
				<item>
			<title>"Умное" сжатие GZIP</title>
			<link>http://new.xpro.su/php/smart-gzip</link>
			<pubDate>Tue, 29 Nov 2011 11:44:00 +0000</pubDate>			<dc:creator>4X_Pro</dc:creator>
			<category domain="alt">Администрирование</category>
<category domain="main">Web-программирование</category>			<guid isPermaLink="false">102@http://new.xpro.su/</guid>
						<description>&lt;p&gt;Как известно, в PHP есть возможность сжимать с помощью GZIP отдаваемые пользователю данные. С одной стороны, оно позволяет сэкономить траффик и увеличить скорость загрузки, что крайне важно для пользователей, подключенных через GPRS или обычные аналоговые модемы, так как сайты сейчас становятся по объему все больше и больше.  С другой — увеличивает нагрузку на сервер: на сжатие требуется дополнительное процессорное время. Кроме того, для пользователей с быстрыми каналами связи эффект от сжатия оказывается настолько незначительным, что время на упаковку/распаковку данных может оказаться вполне сопоставимым со временем, сэкономленным на передаче.&lt;/p&gt;
&lt;p&gt;Сразу возникает мысль: а что если как-то определить, как подключен пользователь, и включать сжатие только в тех случаях, если соединение медленное. Измерить скорость подключения пользователя средствами PHP — задача непростая. Но есть и другой вариант: большинство провайдеров сейчас присваивает пользователям через обратный DNS имя хоста, в котором указан тип подключения. Например, broadband-*.nationalcablenetworks.ru или dialup-*.mtu-net.ru.&lt;/p&gt;
&lt;p&gt;В PHP это имя хоста можо получить из переменной &lt;var class=&quot;varname&quot;&gt;$_SERVER[&#039;REMOTE_HOST&#039;]&lt;/var&gt; (для этого нужно, чтобы в настройках Web-сервера было включено определение обратного DNS, в Apache это делается директивой &lt;em&gt;HostnameLookups On&lt;/em&gt;). Далее можно просто проверить наличие строк gprs и dialup в имени хоста и включить сжатие при их наличии. Вот пример функции, которая реализует такую проверку:&lt;/p&gt;
&lt;pre&gt;$userhost = $_SERVER[&#039;REMOTE_HOST&#039;];
if (strpos($userhost,&#039;dialup&#039;)!==false || strpos($userhost,&#039;gprs&#039;)!==false) ob_start(&#039;ob_gzhandler&#039;);
else ob_start(); &lt;/pre&gt;
&lt;p&gt;Конечно, этот вариант тоже не лишен недостатков: во-первых, он не является полностью надежным, а во-вторых, определение обратного DNS тоже создает небольшую дополнительную нагрузку на сервер, но все же он является более гибким решением, чем принудительное включение или выключение сжатия. Аналогичную проверку можно так же использовать при написании правил для mod_rewrite, для выбора, какой файл JavaScript отдать пользователю: упакованный средствами самого JavaScript (но из-за этого требующий больше времени на исполнение) или просто минифицированный.&lt;/p&gt;&lt;div class=&quot;item_footer&quot;&gt;&lt;p&gt;&lt;small&gt;&lt;a href=&quot;http://new.xpro.su/php/smart-gzip&quot;&gt;Original post&lt;/a&gt; blogged on &lt;a href=&quot;http://b2evolution.net/&quot;&gt;b2evolution&lt;/a&gt;.&lt;/small&gt;&lt;/p&gt;&lt;/div&gt;</description>
			<content:encoded><![CDATA[<p>Как известно, в PHP есть возможность сжимать с помощью GZIP отдаваемые пользователю данные. С одной стороны, оно позволяет сэкономить траффик и увеличить скорость загрузки, что крайне важно для пользователей, подключенных через GPRS или обычные аналоговые модемы, так как сайты сейчас становятся по объему все больше и больше.  С другой — увеличивает нагрузку на сервер: на сжатие требуется дополнительное процессорное время. Кроме того, для пользователей с быстрыми каналами связи эффект от сжатия оказывается настолько незначительным, что время на упаковку/распаковку данных может оказаться вполне сопоставимым со временем, сэкономленным на передаче.</p>
<p>Сразу возникает мысль: а что если как-то определить, как подключен пользователь, и включать сжатие только в тех случаях, если соединение медленное. Измерить скорость подключения пользователя средствами PHP — задача непростая. Но есть и другой вариант: большинство провайдеров сейчас присваивает пользователям через обратный DNS имя хоста, в котором указан тип подключения. Например, broadband-*.nationalcablenetworks.ru или dialup-*.mtu-net.ru.</p>
<p>В PHP это имя хоста можо получить из переменной <var class="varname">$_SERVER['REMOTE_HOST']</var> (для этого нужно, чтобы в настройках Web-сервера было включено определение обратного DNS, в Apache это делается директивой <em>HostnameLookups On</em>). Далее можно просто проверить наличие строк gprs и dialup в имени хоста и включить сжатие при их наличии. Вот пример функции, которая реализует такую проверку:</p>
<pre>$userhost = $_SERVER['REMOTE_HOST'];
if (strpos($userhost,'dialup')!==false || strpos($userhost,'gprs')!==false) ob_start('ob_gzhandler');
else ob_start(); </pre>
<p>Конечно, этот вариант тоже не лишен недостатков: во-первых, он не является полностью надежным, а во-вторых, определение обратного DNS тоже создает небольшую дополнительную нагрузку на сервер, но все же он является более гибким решением, чем принудительное включение или выключение сжатия. Аналогичную проверку можно так же использовать при написании правил для mod_rewrite, для выбора, какой файл JavaScript отдать пользователю: упакованный средствами самого JavaScript (но из-за этого требующий больше времени на исполнение) или просто минифицированный.</p><div class="item_footer"><p><small><a href="http://new.xpro.su/php/smart-gzip">Original post</a> blogged on <a href="http://b2evolution.net/">b2evolution</a>.</small></p></div>]]></content:encoded>
								<comments>http://new.xpro.su/php/smart-gzip#comments</comments>
			<wfw:commentRss>http://new.xpro.su/?tempskin=_rss2&#38;disp=comments&#38;p=102</wfw:commentRss>
		</item>
				<item>
			<title>Сессии в PHP и нагрузка на сервер</title>
			<link>http://new.xpro.su/php/php</link>
			<pubDate>Sat, 26 Nov 2011 09:29:00 +0000</pubDate>			<dc:creator>4X_Pro</dc:creator>
			<category domain="main">Web-программирование</category>			<guid isPermaLink="false">101@http://new.xpro.su/</guid>
						<description>&lt;p&gt;На первый взгляд кажется, что работать с сессиями в PHP предельно  просто. Достаточно написать где-нибудь в начале скрипта такой код:&lt;/p&gt;
&lt;pre&gt;session_name(&#039;MySessId&#039;); // задаем имя cookie или параметра, в котором хранится идентификатор сессии&lt;br /&gt;session_set_cookies_params(24*3600,&#039;/&#039;,&#039;xpro.su&#039;); //если нужно, задаем домен, путь и время хранения для cookie сессии&lt;br /&gt;session_start(); // а теперь запускаем саму сессию&lt;/pre&gt;
&lt;p&gt;и в глобальной переменной $_SESSION можно будет хранить нужные  значения, которые будут доступны при каждом обращении пользователя к  странице! На малых сайтах такое решение работает без проблем, но оно не  будет масштабируемым: как только сайт достигнет нескольких десятков  тысяч страниц или посещаемости в десятки тысяч пользователей в сутки,  может случиться так, что сайт будет открываться весьма и весьма  медленно.&lt;/p&gt;
&lt;p&gt;Чтобы  разобраться, почему такое происходит, рассмотрим, как же работают  сессии в PHP. При вызове session_start  в самый первый раз генерируется  ее идентификатор сессии, который передается пользователю либо в виде  cookie, либо в виде параметра URL, который добавляется ко всем локальным  ссылкам на странице, выдаваемой пользователю. На сервере же создается  файл, имя которого совпадает с этим идентификатором. В конце выполнения  скрипта (или при вызове session_write_close()) данные, помещенные в  $_SESSION, сериализуются и записываются в этот файл. Когда пользователь  следующий раз обращается к скрипту, то от него либо в cookies, либо в  параметрах запроса передается идентификатор сессии (в приведенном выше  примере этот параметр будет называться MySessId), проверяется,  существует ли файл сессии с таким именем, и если да, происходит его  блокировка, считывание и десериализация, после чего данные, сохраненные  при прошлом вызове, снова попадают в $_SESSION.&lt;/p&gt;
&lt;p&gt;Поисковые роботы при индексации сайта не сохраняют установленные  сайтом cookies или параметры с идентификатором сессии. В результате  каждое обращение поискового робота к любой странице сайта будет  приводить к создании новой сессии с новым идентификатором. То есть, если  сайт содержит десять тысяч страниц, то будет создано десять тысяч  файлов сессий! В результате даже простое открытие файла из каталога с  таким количеством файлом становится весьма и весьма медленной операцией.  Если проект работает на отдельном сервере, частично эту проблему можно  решить, задав в настройках PHP хранение сессий не в файлах, а в СУБД или  shared memory, но все равно нагрузка будет достаточно существенная  из-за операций блокировки и сериализации. Также если разрешена передача  идентификатора сессии через GET-запрос, поисковые системы запоминают URL  с этим параметром, что тоже создает определенные проблемы (хотя Яндекс и  Google вроде бы сейчас научились обнаруживать и отбрасывать  идентификатор).&lt;/p&gt;
&lt;p&gt;Но всегда ли нужно создавать сессию прямо при первом же обращении  пользователя к странице? Для большинства проектов это не так. Например,  на форуме необходимость сессии возникает тогда, когда пользователь  решает войти под своим именем, а в Интернет-магазине  — при добавлении  товара в корзину. До этого для показа тем в гостевом режиме или описаний  товаров сессия совершено не требуется. Отсюда вывод: сессию  пользователя нужно создавать только тогда, когда в ней возникает  реальная необходимость. Но с другой стороны, если пользователь вошел на  сайт или добавил что-то в корзину, то нужно на каждой странице  показывать ему, что форум его узнал или что в корзине у него что-то  есть. Таким образом алгоритм действий получается такой: не создаем  сессию, пока она нам не потребовалась, но если уж создали, то  возобновляем ее (т.е. читаем ее данные в $_SESSION) при каждом обращении  к скрипту. Для его реализации подойдет следующая функция:&lt;/p&gt;
&lt;pre&gt;function session($force=false) {
 $s_name=&#039;MySessId&#039;; // имя сессии&lt;br /&gt; static $started;&lt;br /&gt; session_name($s_name);&lt;br /&gt; session_set_cookie_params(24*60*60,&#039;/&#039;,&#039;xpro.su&#039;); // параметры cookies&lt;br /&gt;/* Если от пользователя пришел параметр или cookie с идентификатором  сессии, значит, сессию уже создали и нужно ее возобновить, если же  $force==true, то это явное указание на необходимость создания сессии */&lt;br /&gt; if (isset($_REQUEST[$s_name]) || isset($_COOKIE[$_name])  || $force) { &lt;br /&gt;/* Переменная $started нужна для избежания повторого вызова session_start (например, если добавляем в корзину еще один товар при уже созданной сессии), чтобы не генерировалось лишнее предупреждение. */&lt;br /&gt;   if (!$started) { session_start(); $started=1; } &lt;br /&gt; }&lt;br /&gt;}&lt;/pre&gt;
&lt;p&gt;В скрипте эту функцию нужно вызывать дважды: с параметром false в  начале скрипта для восстановления сессии, если она уже была создана, и с  параметром true при выполнении тех действий, которые требуют  обязательного создания сессии (вход пользователя на форум, добавление  товара в корзину и т.п.).&lt;/p&gt;
&lt;p&gt;Знание таких особенностей и использование предложенной функции  позволит не только снизить нагрузку при прохождении по сайту поисковых  роботов, но и сэкономить при этом по несколько сотен байтов на каждый  запрос, так как без необходимости не будет передаваться заголовок  Set-cookie или параметры с идентификатором сессии.&lt;/p&gt;&lt;div class=&quot;item_footer&quot;&gt;&lt;p&gt;&lt;small&gt;&lt;a href=&quot;http://new.xpro.su/php/php&quot;&gt;Original post&lt;/a&gt; blogged on &lt;a href=&quot;http://b2evolution.net/&quot;&gt;b2evolution&lt;/a&gt;.&lt;/small&gt;&lt;/p&gt;&lt;/div&gt;</description>
			<content:encoded><![CDATA[<p>На первый взгляд кажется, что работать с сессиями в PHP предельно  просто. Достаточно написать где-нибудь в начале скрипта такой код:</p>
<pre>session_name('MySessId'); // задаем имя cookie или параметра, в котором хранится идентификатор сессии<br />session_set_cookies_params(24*3600,'/','xpro.su'); //если нужно, задаем домен, путь и время хранения для cookie сессии<br />session_start(); // а теперь запускаем саму сессию</pre>
<p>и в глобальной переменной $_SESSION можно будет хранить нужные  значения, которые будут доступны при каждом обращении пользователя к  странице! На малых сайтах такое решение работает без проблем, но оно не  будет масштабируемым: как только сайт достигнет нескольких десятков  тысяч страниц или посещаемости в десятки тысяч пользователей в сутки,  может случиться так, что сайт будет открываться весьма и весьма  медленно.</p>
<p>Чтобы  разобраться, почему такое происходит, рассмотрим, как же работают  сессии в PHP. При вызове session_start  в самый первый раз генерируется  ее идентификатор сессии, который передается пользователю либо в виде  cookie, либо в виде параметра URL, который добавляется ко всем локальным  ссылкам на странице, выдаваемой пользователю. На сервере же создается  файл, имя которого совпадает с этим идентификатором. В конце выполнения  скрипта (или при вызове session_write_close()) данные, помещенные в  $_SESSION, сериализуются и записываются в этот файл. Когда пользователь  следующий раз обращается к скрипту, то от него либо в cookies, либо в  параметрах запроса передается идентификатор сессии (в приведенном выше  примере этот параметр будет называться MySessId), проверяется,  существует ли файл сессии с таким именем, и если да, происходит его  блокировка, считывание и десериализация, после чего данные, сохраненные  при прошлом вызове, снова попадают в $_SESSION.</p>
<p>Поисковые роботы при индексации сайта не сохраняют установленные  сайтом cookies или параметры с идентификатором сессии. В результате  каждое обращение поискового робота к любой странице сайта будет  приводить к создании новой сессии с новым идентификатором. То есть, если  сайт содержит десять тысяч страниц, то будет создано десять тысяч  файлов сессий! В результате даже простое открытие файла из каталога с  таким количеством файлом становится весьма и весьма медленной операцией.  Если проект работает на отдельном сервере, частично эту проблему можно  решить, задав в настройках PHP хранение сессий не в файлах, а в СУБД или  shared memory, но все равно нагрузка будет достаточно существенная  из-за операций блокировки и сериализации. Также если разрешена передача  идентификатора сессии через GET-запрос, поисковые системы запоминают URL  с этим параметром, что тоже создает определенные проблемы (хотя Яндекс и  Google вроде бы сейчас научились обнаруживать и отбрасывать  идентификатор).</p>
<p>Но всегда ли нужно создавать сессию прямо при первом же обращении  пользователя к странице? Для большинства проектов это не так. Например,  на форуме необходимость сессии возникает тогда, когда пользователь  решает войти под своим именем, а в Интернет-магазине  — при добавлении  товара в корзину. До этого для показа тем в гостевом режиме или описаний  товаров сессия совершено не требуется. Отсюда вывод: сессию  пользователя нужно создавать только тогда, когда в ней возникает  реальная необходимость. Но с другой стороны, если пользователь вошел на  сайт или добавил что-то в корзину, то нужно на каждой странице  показывать ему, что форум его узнал или что в корзине у него что-то  есть. Таким образом алгоритм действий получается такой: не создаем  сессию, пока она нам не потребовалась, но если уж создали, то  возобновляем ее (т.е. читаем ее данные в $_SESSION) при каждом обращении  к скрипту. Для его реализации подойдет следующая функция:</p>
<pre>function session($force=false) {
 $s_name='MySessId'; // имя сессии<br /> static $started;<br /> session_name($s_name);<br /> session_set_cookie_params(24*60*60,'/','xpro.su'); // параметры cookies<br />/* Если от пользователя пришел параметр или cookie с идентификатором  сессии, значит, сессию уже создали и нужно ее возобновить, если же  $force==true, то это явное указание на необходимость создания сессии */<br /> if (isset($_REQUEST[$s_name]) || isset($_COOKIE[$_name])  || $force) { <br />/* Переменная $started нужна для избежания повторого вызова session_start (например, если добавляем в корзину еще один товар при уже созданной сессии), чтобы не генерировалось лишнее предупреждение. */<br />   if (!$started) { session_start(); $started=1; } <br /> }<br />}</pre>
<p>В скрипте эту функцию нужно вызывать дважды: с параметром false в  начале скрипта для восстановления сессии, если она уже была создана, и с  параметром true при выполнении тех действий, которые требуют  обязательного создания сессии (вход пользователя на форум, добавление  товара в корзину и т.п.).</p>
<p>Знание таких особенностей и использование предложенной функции  позволит не только снизить нагрузку при прохождении по сайту поисковых  роботов, но и сэкономить при этом по несколько сотен байтов на каждый  запрос, так как без необходимости не будет передаваться заголовок  Set-cookie или параметры с идентификатором сессии.</p><div class="item_footer"><p><small><a href="http://new.xpro.su/php/php">Original post</a> blogged on <a href="http://b2evolution.net/">b2evolution</a>.</small></p></div>]]></content:encoded>
								<comments>http://new.xpro.su/php/php#comments</comments>
			<wfw:commentRss>http://new.xpro.su/?tempskin=_rss2&#38;disp=comments&#38;p=101</wfw:commentRss>
		</item>
				<item>
			<title>Отладка PHP-скриптов</title>
			<link>http://new.xpro.su/php/php-debug</link>
			<pubDate>Tue, 15 Nov 2011 17:05:00 +0000</pubDate>			<dc:creator>4X_Pro</dc:creator>
			<category domain="main">Web-программирование</category>			<guid isPermaLink="false">100@http://new.xpro.su/</guid>
						<description>&lt;p&gt;Как известно, при возникновении ошибки PHP в лучшем случае выдает номер строки, где она произошла, и ее краткое описание, а в худшем (если в настройках хостинга отключен показ ошибок вообще) — просто пустую страницу. Это не слишком удобно как для отладки, так и для конечного пользователя. Возникает вопрос: как сделать вывод сообщения об ошибке более информативным.&lt;/p&gt;
&lt;p&gt;Оказывается, все достаточно просто. В PHP существует специальная функция set_error_handler(), которая позволяет задать свой собственный обработчик ошибок.Единственный ее параметр -- это имя функции-обработчика, которая вызывается в случае возникновения ошибки. Функция-обработчик имеет 4 параметра: номер ошибки, текст ошибки, имя файла, в котором ошибка произошла и номер строки в этом файле.&lt;/p&gt;

&lt;p&gt;Но этих данных может быть недостаточно. Часто требуется вывести и весь стек вызовов, чтобы понять, где именно произошла ошибка. Получить стек вызвов можно с помощью функции debug_backtrace, которая возвращает массив обратных вызовов. Каждый элемент этого массива является хешем со следующими полями: function -- имя функции, args -- аргументы функции, file -- имя файла скрипта, в котором она была вызвана. line -- номер строки, где был выполнен вызов функции.&lt;/p&gt;
&lt;p&gt;Внимание: в ряде случаев file и line бывают не заданы, всегда проверяйте их (да и вообще любые индексы массива) с помощью isset, а константы -- с помощью defined, иначе возникнет рекурсивный вызов обработчика ошибок. Кроме того, если сделать вывод аргументов функций, нужно принять меры, чтобы не выдавался пароль от базы данных в том случае, если ошибка возникнет на этапе подключения к СУБД (т.е. при вызове функции mysql_connect или ей подобной). Самый простой вариант -- заменять его с помощью str_replace на звездочки или иные спецсимволы перед выводом.&lt;/p&gt;
&lt;p&gt;Рассмотрим пример функции-обработчика:&lt;/p&gt;
&lt;pre&gt; function error_handler($errno, $errstr, $errfile, $errline) {&lt;br /&gt; $dbg_on = defined(&#039;CONFIG_debug&#039;) ? CONFIG_debug : false;     &lt;br /&gt; $debug=debug_backtrace();     &lt;br /&gt; $errmsg=&#039;&amp;lt;p&amp;gt;&#039;.$errstr.&#039; (строка &#039;.$errline.&#039;, &#039;.$errfile.&#039;, ошибка: &#039;.$errno.&#039;)&#039;.&#039;&amp;lt;/p&amp;gt;&amp;lt;ul style=&quot;font-size: 0.9em; color: #600&quot;&amp;gt;&#039;;     &lt;br /&gt; $count=count($debug);     &lt;br /&gt; for ($i=1; $i&amp;lt;$count; $i++) { // обрабатываем с единицы, так как нулевой элемент -- это вызов самого обработчика error_handler       &lt;br /&gt; $errmsg.=&#039;&amp;lt;li&amp;gt;&#039;.$debug[$i][&#039;function&#039;].&#039;()&#039;.&lt;br /&gt;               &#039; — &#039;.((isset($debug[$i][&#039;file&#039;])) ? $debug[$i][&#039;file&#039;] : &#039;неизвестный файл&#039;).&#039;, &#039;.&lt;br /&gt;               &#039;строка &#039;.((isset($debug[$i][&#039;line&#039;])) ? $debug[$i][&#039;line&#039;] : &#039;неизвестна&#039;).&#039;&amp;lt;/li&amp;gt;&#039;; //.var_dump($debug[$i][&#039;args&#039;])     &lt;br /&gt; }       $errmsg.=&#039;&amp;lt;/ul&amp;gt;&#039;;      &lt;br /&gt; if (($errno &amp;amp; E_ERROR) || ($errno &amp;amp; E_USER_ERROR)) { // если ошибка фатальная и требует прекращения работы скрипта       &lt;br /&gt; if (!headers_sent()) { // если заголовок страницы еще не был отправлен&lt;br /&gt; header($_SERVER[&#039;SERVER_PROTOCOL&#039;].&#039; 500 Internal Server Error&#039;);         &lt;br /&gt; header(&#039;Content-Type: text/html; charset=utf-8&#039;);         &lt;br /&gt; echo &#039;&amp;lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.01 Transitional//EN&quot; 				&quot;http://www.w3.org/TR/html4/loose.dtd&quot;&amp;gt;&lt;br /&gt; &amp;lt;head&amp;gt;&lt;br /&gt; &amp;lt;title&amp;gt;Ошибка сайта&amp;lt;/title&amp;gt;&lt;br /&gt; &amp;lt;meta http-equiv=&quot;Content-Type&quot; content=&quot;text/html; charset=utf-8&quot;&amp;gt;&lt;br /&gt;&amp;lt;/head&amp;gt;&amp;lt;body&amp;gt;&#039;;       &lt;br /&gt; }        &lt;br /&gt; $email = defined(&#039;CONFIG_email&#039;) ? CONFIG_email : &#039;&#039;; &lt;br /&gt; echo &#039;&amp;lt;div style=&quot;font-size: 1em; padding: 4px; font-weight: bold; color: #C44; border: #C00 1px solid; margin: 4px&quot;&amp;gt;На сайте произошла ошибка. Попробуйте обновить страницу через несколько минут. Если ошибка не исчезнет, пожалуйста, сообщите о ней администратору сайта по адресу &amp;lt;a href=&quot;mailto:&#039;.$email.&#039;&quot;&amp;gt;&#039;.$email.&#039;&amp;lt;/a&amp;gt; &#039;;       &lt;br /&gt; if ($dbg_on) {         &lt;br /&gt; echo &#039;&amp;lt;p&amp;gt;&#039;.$errmsg.&#039;&amp;lt;/p&amp;gt;&#039;;         &lt;br /&gt;}       &lt;br /&gt; echo &#039;&amp;lt;/div&amp;gt;&amp;lt;/body&amp;gt;&amp;lt;/html&amp;gt;&#039;;     &lt;br /&gt; }     &lt;br /&gt; else {&lt;br /&gt; // здесь можно вставить обработку предупреждений (warning) и примечаний (notice), например, с помощью функции _dbg, о которой см. ниже&lt;br /&gt; }&lt;br /&gt;}&lt;/pre&gt;
&lt;p&gt;Эта функция получает стек вызова и проверяет, включен ли отладочный режим (он определяется константой CONFIG_debug, которая задается, например, в файле конфигурации вместе с настройками подключения к БД). Дальше идет проверка, отправлены ли заголовки (т.е. делал ли скрипт до этого какой-то вывод). Если нет, то выдается статус 500 (внутренняя ошибка сервера), чтобы поисковые системы не воспринимали сообщение об ошибке как контент, который нужно индексировать, и формируется страница с сообщением об ошибке и предложением написать администратору, адрес которого задан в константе CONFIG_email. Если отладочный режим включен, то выдается и полный стек вызовов функций перед ошибкой. При необходимости в эту же функцию можно добавить сохранение отладочной информации в лог-файл.&lt;/p&gt;
&lt;p&gt;Также часто при отладке скриптов требуется вывести значение какой-либо переменной, чтобы понять, на каком этапе берутся неправильные данные. Использование для этого print_r — не самое лучшее решение (особенно при отладке на живом сайте), так как значение может вывестись в неподходящем месте (например, если используется шаблонизатор, то еще до HTML-заголовка). Кроме того, Более корректное решение &amp;mdash; запомнить значение в отладочную переменную, которую вывести потом там, где ее появление не будет мешать (например, в подвале сайта).&lt;/p&gt;
&lt;p&gt;Я для этих целей использую такую функцию:&lt;/p&gt;
&lt;pre&gt;function _dbg() {&lt;br /&gt;
  $dbg_on = defined(&#039;CONFIG_debug&#039;) ? CONFIG_debug : false;&lt;br /&gt;
  if ($dbg_on) {&lt;br /&gt;
    if (!isset($GLOBALS[&#039;IntBF_debug&#039;])) $GLOBALS[&#039;IntBF_debug&#039;]=&#039;&#039;;&lt;br /&gt;
    $GLOBALS[&#039;IntBF_debug&#039;].=&#039;&amp;lt;p&amp;gt;&#039;;&lt;br /&gt;
    foreach (func_get_args() as $name=&amp;gt;$value) {&lt;br /&gt;
      if (is_array($value) || is_object($value)) $GLOBALS[&#039;IntBF_debug&#039;].=$name.&#039;: &#039;.nl2br(str_replace(&#039;  &#039;,&#039;&amp;amp;nbsp;&#039;,htmlspecialchars(print_r($value,true))));&lt;br /&gt;
      else $GLOBALS[&#039;IntBF_debug&#039;].=$name.&#039;: &#039;.htmlspecialchars($value).&#039; &#039;;&lt;br /&gt;
    }&lt;br /&gt;
    $GLOBALS[&#039;IntBF_debug&#039;].=&quot;&amp;lt;/p&amp;gt;\n&quot;;&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&lt;/pre&gt;
&lt;p&gt;Эта функция экранирует все переданные в нее параметры и запоминает в глобальную переменную $GLOBALS[&#039;IntBF_debug&#039;], из которой ее можно вывести в любом подходящем месте с помощью обычного echo.&lt;/p&gt;
&lt;p&gt;Если требуется прервать выполнение скрипта с ошибкой по инициативе разработчика (например, после ошибочного SQL-запроса), следует использовать функцию trigger_error. У этой функции два параметра: первый -- строка с сообщением об ошибке, которая будет передана в параметр $errstr обработчика, а второй -- код ошибки, который может быть одной из трех констант: E_USER_ERROR (приведет к завершению выполнения), E_USER_WARNING, E_USER_NOTICE.&lt;/p&gt;
&lt;p&gt;Использование этих функций может помочь значительно сэкономить немало времени и сделать сайт дружелюбнее к пользователю. Но в то же время не следует забывать, что есть ситуации, в которых этот обработчик не сработает, например, при вызове несуществующей функции или подключении с помощью require скрипта, содержащего синтаксическую ошибку.&lt;/p&gt;&lt;div class=&quot;item_footer&quot;&gt;&lt;p&gt;&lt;small&gt;&lt;a href=&quot;http://new.xpro.su/php/php-debug&quot;&gt;Original post&lt;/a&gt; blogged on &lt;a href=&quot;http://b2evolution.net/&quot;&gt;b2evolution&lt;/a&gt;.&lt;/small&gt;&lt;/p&gt;&lt;/div&gt;</description>
			<content:encoded><![CDATA[<p>Как известно, при возникновении ошибки PHP в лучшем случае выдает номер строки, где она произошла, и ее краткое описание, а в худшем (если в настройках хостинга отключен показ ошибок вообще) — просто пустую страницу. Это не слишком удобно как для отладки, так и для конечного пользователя. Возникает вопрос: как сделать вывод сообщения об ошибке более информативным.</p>
<p>Оказывается, все достаточно просто. В PHP существует специальная функция set_error_handler(), которая позволяет задать свой собственный обработчик ошибок.Единственный ее параметр -- это имя функции-обработчика, которая вызывается в случае возникновения ошибки. Функция-обработчик имеет 4 параметра: номер ошибки, текст ошибки, имя файла, в котором ошибка произошла и номер строки в этом файле.</p>

<p>Но этих данных может быть недостаточно. Часто требуется вывести и весь стек вызовов, чтобы понять, где именно произошла ошибка. Получить стек вызвов можно с помощью функции debug_backtrace, которая возвращает массив обратных вызовов. Каждый элемент этого массива является хешем со следующими полями: function -- имя функции, args -- аргументы функции, file -- имя файла скрипта, в котором она была вызвана. line -- номер строки, где был выполнен вызов функции.</p>
<p>Внимание: в ряде случаев file и line бывают не заданы, всегда проверяйте их (да и вообще любые индексы массива) с помощью isset, а константы -- с помощью defined, иначе возникнет рекурсивный вызов обработчика ошибок. Кроме того, если сделать вывод аргументов функций, нужно принять меры, чтобы не выдавался пароль от базы данных в том случае, если ошибка возникнет на этапе подключения к СУБД (т.е. при вызове функции mysql_connect или ей подобной). Самый простой вариант -- заменять его с помощью str_replace на звездочки или иные спецсимволы перед выводом.</p>
<p>Рассмотрим пример функции-обработчика:</p>
<pre> function error_handler($errno, $errstr, $errfile, $errline) {<br /> $dbg_on = defined('CONFIG_debug') ? CONFIG_debug : false;     <br /> $debug=debug_backtrace();     <br /> $errmsg='&lt;p&gt;'.$errstr.' (строка '.$errline.', '.$errfile.', ошибка: '.$errno.')'.'&lt;/p&gt;&lt;ul style="font-size: 0.9em; color: #600"&gt;';     <br /> $count=count($debug);     <br /> for ($i=1; $i&lt;$count; $i++) { // обрабатываем с единицы, так как нулевой элемент -- это вызов самого обработчика error_handler       <br /> $errmsg.='&lt;li&gt;'.$debug[$i]['function'].'()'.<br />               ' — '.((isset($debug[$i]['file'])) ? $debug[$i]['file'] : 'неизвестный файл').', '.<br />               'строка '.((isset($debug[$i]['line'])) ? $debug[$i]['line'] : 'неизвестна').'&lt;/li&gt;'; //.var_dump($debug[$i]['args'])     <br /> }       $errmsg.='&lt;/ul&gt;';      <br /> if (($errno &amp; E_ERROR) || ($errno &amp; E_USER_ERROR)) { // если ошибка фатальная и требует прекращения работы скрипта       <br /> if (!headers_sent()) { // если заголовок страницы еще не был отправлен<br /> header($_SERVER['SERVER_PROTOCOL'].' 500 Internal Server Error');         <br /> header('Content-Type: text/html; charset=utf-8');         <br /> echo '&lt;!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" 				"http://www.w3.org/TR/html4/loose.dtd"&gt;<br /> &lt;head&gt;<br /> &lt;title&gt;Ошибка сайта&lt;/title&gt;<br /> &lt;meta http-equiv="Content-Type" content="text/html; charset=utf-8"&gt;<br />&lt;/head&gt;&lt;body&gt;';       <br /> }        <br /> $email = defined('CONFIG_email') ? CONFIG_email : ''; <br /> echo '&lt;div style="font-size: 1em; padding: 4px; font-weight: bold; color: #C44; border: #C00 1px solid; margin: 4px"&gt;На сайте произошла ошибка. Попробуйте обновить страницу через несколько минут. Если ошибка не исчезнет, пожалуйста, сообщите о ней администратору сайта по адресу &lt;a href="mailto:'.$email.'"&gt;'.$email.'&lt;/a&gt; ';       <br /> if ($dbg_on) {         <br /> echo '&lt;p&gt;'.$errmsg.'&lt;/p&gt;';         <br />}       <br /> echo '&lt;/div&gt;&lt;/body&gt;&lt;/html&gt;';     <br /> }     <br /> else {<br /> // здесь можно вставить обработку предупреждений (warning) и примечаний (notice), например, с помощью функции _dbg, о которой см. ниже<br /> }<br />}</pre>
<p>Эта функция получает стек вызова и проверяет, включен ли отладочный режим (он определяется константой CONFIG_debug, которая задается, например, в файле конфигурации вместе с настройками подключения к БД). Дальше идет проверка, отправлены ли заголовки (т.е. делал ли скрипт до этого какой-то вывод). Если нет, то выдается статус 500 (внутренняя ошибка сервера), чтобы поисковые системы не воспринимали сообщение об ошибке как контент, который нужно индексировать, и формируется страница с сообщением об ошибке и предложением написать администратору, адрес которого задан в константе CONFIG_email. Если отладочный режим включен, то выдается и полный стек вызовов функций перед ошибкой. При необходимости в эту же функцию можно добавить сохранение отладочной информации в лог-файл.</p>
<p>Также часто при отладке скриптов требуется вывести значение какой-либо переменной, чтобы понять, на каком этапе берутся неправильные данные. Использование для этого print_r — не самое лучшее решение (особенно при отладке на живом сайте), так как значение может вывестись в неподходящем месте (например, если используется шаблонизатор, то еще до HTML-заголовка). Кроме того, Более корректное решение &mdash; запомнить значение в отладочную переменную, которую вывести потом там, где ее появление не будет мешать (например, в подвале сайта).</p>
<p>Я для этих целей использую такую функцию:</p>
<pre>function _dbg() {<br />
  $dbg_on = defined('CONFIG_debug') ? CONFIG_debug : false;<br />
  if ($dbg_on) {<br />
    if (!isset($GLOBALS['IntBF_debug'])) $GLOBALS['IntBF_debug']='';<br />
    $GLOBALS['IntBF_debug'].='&lt;p&gt;';<br />
    foreach (func_get_args() as $name=&gt;$value) {<br />
      if (is_array($value) || is_object($value)) $GLOBALS['IntBF_debug'].=$name.': '.nl2br(str_replace('  ','&amp;nbsp;',htmlspecialchars(print_r($value,true))));<br />
      else $GLOBALS['IntBF_debug'].=$name.': '.htmlspecialchars($value).' ';<br />
    }<br />
    $GLOBALS['IntBF_debug'].="&lt;/p&gt;\n";<br />
  }<br />
}<br />
</pre>
<p>Эта функция экранирует все переданные в нее параметры и запоминает в глобальную переменную $GLOBALS['IntBF_debug'], из которой ее можно вывести в любом подходящем месте с помощью обычного echo.</p>
<p>Если требуется прервать выполнение скрипта с ошибкой по инициативе разработчика (например, после ошибочного SQL-запроса), следует использовать функцию trigger_error. У этой функции два параметра: первый -- строка с сообщением об ошибке, которая будет передана в параметр $errstr обработчика, а второй -- код ошибки, который может быть одной из трех констант: E_USER_ERROR (приведет к завершению выполнения), E_USER_WARNING, E_USER_NOTICE.</p>
<p>Использование этих функций может помочь значительно сэкономить немало времени и сделать сайт дружелюбнее к пользователю. Но в то же время не следует забывать, что есть ситуации, в которых этот обработчик не сработает, например, при вызове несуществующей функции или подключении с помощью require скрипта, содержащего синтаксическую ошибку.</p><div class="item_footer"><p><small><a href="http://new.xpro.su/php/php-debug">Original post</a> blogged on <a href="http://b2evolution.net/">b2evolution</a>.</small></p></div>]]></content:encoded>
								<comments>http://new.xpro.su/php/php-debug#comments</comments>
			<wfw:commentRss>http://new.xpro.su/?tempskin=_rss2&#38;disp=comments&#38;p=100</wfw:commentRss>
		</item>
				<item>
			<title>Правильная выдача заголовка Content-Length</title>
			<link>http://new.xpro.su/php/content-length</link>
			<pubDate>Wed, 31 Aug 2011 22:57:00 +0000</pubDate>			<dc:creator>4X_Pro</dc:creator>
			<category domain="main">Web-программирование</category>			<guid isPermaLink="false">99@http://new.xpro.su/</guid>
						<description>&lt;p&gt;Однажды, скачивая один из своих сайтов с помощью WGet, с удивлением обнаружил, что после каждой страницы программа останавливается и ждет несколько секунд. Да пользователи иногда тоже жаловались на то, что загрузка сайта очень долго не заканчивается. Стал разбираться, в чем дело, и вот что удалось выяснить.&lt;/p&gt;
&lt;p&gt;Как известно, в HTTP-протоколе есть три способа сообщить клиенту о том, что передача завершена. Первый, самый старый, -- разорвать соединение после того, как весь контент передан. Он же самый медленный, так как при этом не работает механизм Keep-Alive, и для следующего запроса клиент должен установить новое соединение. Второй -- это передача в заголовке Content-Length длины (в байтах) передаваемого контента. И наконец, третий -- передача контента блоками, перед каждым из которых передается его длина в шестнадцатеричном виде, а завершение передачи обозначается передачей блока с нулевой длиной (так наказываемый chunked transfer, возможно, о нем я напишу со временем отдельную запись).&lt;/p&gt;
&lt;p&gt;По каким-то причинам я считал, что в случае использования PHP используется chunked transfer, причем длину chunks определяет и выдает либо сам Apache, либо модуль PHP. Увы, это оказалось не так, и на моем сайте использовался самый медленный первый способ -- закрытие соединения (причем не сразу, а после какого-то тайматуа, что и вызывало задержки).&lt;/p&gt;
&lt;p&gt;Возник вопрос, как выводить Content-Length. На первый взгляд, все просто: собрать весь выводимый HTML-код в строку-буфер, посчитать ее длину и выдать. Но во-первых, в некоторых скриптах выдача делалась сразу, а не через буфер, а во-вторых, если включено сжатие GZip, реальная длина отдаваемого контента меньше переданной в Content-Length, и клиент ждет оставшейся части до тех пор, пока не истечет таймаут соединения. (Особенно плохо к этому относятся поисковики, от такого сайт может даже выпасть из выдачи.)&lt;/p&gt;
&lt;p&gt;Тем не менее, решение все же есть. Нужно использовать встроенный механизм буферизации вывода в PHP, в частности, функцию ob_get_length, позволюящую узнать реальную длину этого буфера. Выглядит это примерно так:&lt;/p&gt;
&lt;pre&gt;&amp;lt;?php
ob_start(&#039;ob_gzhandler&#039;);
ob_implicit_flush(0); // отключаем неявную отправку буфера
/* Здесь идет код скрипта, в нем не должно быть ob_flush, так как потом нельзя будет выдавать заголовки */
if (!header_sent()) header(&#039;Content-Length: &#039;.ob_get_length()); // если заголовки еще можно отправить, выдаем загловок Content-Length, иначе придется завершать передачу по закрытию&lt;br /&gt;
ob_end_flush();
exit(0);&lt;/pre&gt;&lt;div class=&quot;item_footer&quot;&gt;&lt;p&gt;&lt;small&gt;&lt;a href=&quot;http://new.xpro.su/php/content-length&quot;&gt;Original post&lt;/a&gt; blogged on &lt;a href=&quot;http://b2evolution.net/&quot;&gt;b2evolution&lt;/a&gt;.&lt;/small&gt;&lt;/p&gt;&lt;/div&gt;</description>
			<content:encoded><![CDATA[<p>Однажды, скачивая один из своих сайтов с помощью WGet, с удивлением обнаружил, что после каждой страницы программа останавливается и ждет несколько секунд. Да пользователи иногда тоже жаловались на то, что загрузка сайта очень долго не заканчивается. Стал разбираться, в чем дело, и вот что удалось выяснить.</p>
<p>Как известно, в HTTP-протоколе есть три способа сообщить клиенту о том, что передача завершена. Первый, самый старый, -- разорвать соединение после того, как весь контент передан. Он же самый медленный, так как при этом не работает механизм Keep-Alive, и для следующего запроса клиент должен установить новое соединение. Второй -- это передача в заголовке Content-Length длины (в байтах) передаваемого контента. И наконец, третий -- передача контента блоками, перед каждым из которых передается его длина в шестнадцатеричном виде, а завершение передачи обозначается передачей блока с нулевой длиной (так наказываемый chunked transfer, возможно, о нем я напишу со временем отдельную запись).</p>
<p>По каким-то причинам я считал, что в случае использования PHP используется chunked transfer, причем длину chunks определяет и выдает либо сам Apache, либо модуль PHP. Увы, это оказалось не так, и на моем сайте использовался самый медленный первый способ -- закрытие соединения (причем не сразу, а после какого-то тайматуа, что и вызывало задержки).</p>
<p>Возник вопрос, как выводить Content-Length. На первый взгляд, все просто: собрать весь выводимый HTML-код в строку-буфер, посчитать ее длину и выдать. Но во-первых, в некоторых скриптах выдача делалась сразу, а не через буфер, а во-вторых, если включено сжатие GZip, реальная длина отдаваемого контента меньше переданной в Content-Length, и клиент ждет оставшейся части до тех пор, пока не истечет таймаут соединения. (Особенно плохо к этому относятся поисковики, от такого сайт может даже выпасть из выдачи.)</p>
<p>Тем не менее, решение все же есть. Нужно использовать встроенный механизм буферизации вывода в PHP, в частности, функцию ob_get_length, позволюящую узнать реальную длину этого буфера. Выглядит это примерно так:</p>
<pre>&lt;?php
ob_start('ob_gzhandler');
ob_implicit_flush(0); // отключаем неявную отправку буфера
/* Здесь идет код скрипта, в нем не должно быть ob_flush, так как потом нельзя будет выдавать заголовки */
if (!header_sent()) header('Content-Length: '.ob_get_length()); // если заголовки еще можно отправить, выдаем загловок Content-Length, иначе придется завершать передачу по закрытию<br />
ob_end_flush();
exit(0);</pre><div class="item_footer"><p><small><a href="http://new.xpro.su/php/content-length">Original post</a> blogged on <a href="http://b2evolution.net/">b2evolution</a>.</small></p></div>]]></content:encoded>
								<comments>http://new.xpro.su/php/content-length#comments</comments>
			<wfw:commentRss>http://new.xpro.su/?tempskin=_rss2&#38;disp=comments&#38;p=99</wfw:commentRss>
		</item>
				<item>
			<title>Социальная сеть будущего</title>
			<link>http://new.xpro.su/ideas/social-network</link>
			<pubDate>Mon, 29 Aug 2011 21:03:00 +0000</pubDate>			<dc:creator>4X_Pro</dc:creator>
			<category domain="main">Идеи и размышления</category>			<guid isPermaLink="false">98@http://new.xpro.su/</guid>
						<description>&lt;p&gt;Идея социальных сетей в том виде, в котором они есть сейчас, начинает понемногу исчерпывать себя. И одна из причин этого -- обязательное требование указания реального имени, что противоречит самой идее Интернета, в котором нормой является анонимное общение. На начальном этапе такое требование можно было понять: социальные сети были ориентированы на то, чтобы дать людям, давно потерявшим друг друга, восстановить связь между собой, и использование реального имени значительно упрощало эту задачу. Но сейчас это уже не столь актуально: большинство тех, кто хотел найти друг друга, уже нашли.&lt;br /&gt;Таким образом, прежняя задача социальных сетей выполнена, и уже сейчас наметилась новая: профиль социальной сети для человека становится местом, с которого начинается его субъективный Интернет, перекрестком, который связывает в единое целое его активность на различных сайтах, личным &quot;аналитическим центром&quot;, который собирает для данного человека информацию в соответствии с его интересами и кругом общения. Я решил написать небольшой обзор того, какой эту сеть будущего вижу я.&lt;br /&gt;Что же для этого нужно? Во-первых, разрешить анонимную регистрацию в социальных сетях. Во-вторых, еще более усилить интеграцию между самой социальной сетью и сторонними сайтами. Уже сейчас на многие сайты можно войти без дополнительной регистрации, но если пользователь успел уже создать отдельный профиль, то входить через соцсеть он не будет, чтобы не возникало неоднозначности. Отсюда вывод: нужна возможность динамического преобразования профилей, причем в обе стороны (т.е. человек может и привязать свой профиль на сайте к соц. сети и в любой момент отвязать его). Кроме того, регистрация через соц. сеть должна предусматривать автоматическую передачу (а возможно, и синхронизацию) полей &quot;О себе&quot; и &quot;Интересы&quot;. &lt;br /&gt;В-третьих, основнополагающей идеей этой социальной сети будет &quot;человек -- это то, с чем он себя ассоциирует&quot;. Это позволит решить проблему поиска человека в условиях анонимной сети. Поиск будет осуществляться, во-первых, через контакты (однозначно должен быть поиск по ICQ, Email, Jabber, Skype), а во-вторых, у человека должна быть возможность указать все сайты, которые он ассоциирует с собой и свои имена на них (в принципе, ВКонтакте уже пытались сделать что-то аналогичное, добавив возможность указывать форум и свое Сетевое имя на нем, но почему-то можно было указать только один), и найти его можно будет указав форумное имя и адрес сайта (если, конечно, он сочтет нужным указать его в настройках). &lt;br /&gt;В-четвертых, нужна более тесная интеграция обсуждений. Т.е. оставив комментарий на каком-то форуме, человек может отметить его у себя в профиле соц.сети и видеть ответы других пользователей (но при этом вовсе не обязательно тащить это на публично видимую стену, как это делается ВКонтакте). Причем на самом форуме этот комментарий будет выглядеть как обычное сообщение, а не размещенное именно через социальную сеть, как это делается сейчас (хотя не очень представляю, как это реализовать технически, разве что через экспорт сообщений через RSS, начиная с определенного).&lt;br /&gt;В-пятых, для любого сайта, к которому проявил какой-либо интерес хоть один из участников соц. сети будет создаваться своя страничка, на которой могут быть отзывы об этом сайте, предложения по доделке с сортировкой их по степени значимости (видел такой отдельный сервис, но не помню, как называется) и оценка его полезности участниками. При этом администрация сайта при желании сможет взять частичный контроль над этой страницей (например, настроить ее внешний вид и организовать автоматизированную выдачу туда новостей с сайта или информации о предстоящих событиях, на которые смогут подписываться заинтересованные пользователи), но не сможет эти отзывы стирать. Таким образом, получаем социальную сеть не только людей, но и сайтов. (Нечто подобное пытались сделать на TooDoo пару лет назад, но во-первых, сайты добавлялись только вручную, а во-вторых, просто не смогли раскрутиться.)&lt;br /&gt;В-шестых, в такой социальной сети будет возможность настроить автоматическое отслеживание появления новых сайтов, событий и сообществ на интересующие пользователя темы. При этом так же будет интеллектуальная сортировка значимости этих событий (на основе рейтинга и количества времени, уделяемого источнику этих событий самим пользователем и его кругом общения) с возможностью выставить ограничение на количество предлагаемых событий/новостей.&lt;br /&gt;В-седьмых, можно будет указвать свои предпочтения в плане книг, фильмов, компьютерных игр, и с одной стороны, использовать их при поиске людей, с другой -- получать на основании них рекомендации (аналогично сервису IMHO.NET). &lt;br /&gt;Вся информация о событиях, комментариях, новостях и т.п. будет сводиться на одну страницу с удобным настраиваемым интерфейсом (что-то вроде главной страницы залогиненного Google-пользователя), так же будет предусмотрен календарь/планировщик для планирования собственного времени и дел. Будет возможность временно убирать ненужные события и сортировать их (и сообщества, их производящие) по категориям, показывая только часть из них, а так же, например, добровольно блокировать часть возможностей сети на определенный период (например, отключать личные сообщения от категории &quot;Друзья&quot; на время работы или закрывать доступ в сообщества). Пользователи тоже будут сортироваться по категориям, но при этом эта сортировка будет видна только владельцу профиля (а не как сейчас в G+ и Контакте).&lt;br /&gt;Возможно, не лишней будет интеграция с платежной системой (с инфраструктурой, аналогичной WM, т.е. добровольная аттестация, арбитраж, BL и т.п.), хотя возможно, это лишнее и достаточно просто предусмотреть в WM возможность дать ссылку на эту соц. сеть.&lt;br /&gt;Одной из основных проблем в данной сети будет спам и просто информационный шум (т.е. когда добавляются в друзья и начинают слать разную ерунду). Борьба с этим будет вестись с помощью Социального Рейтинга, который будет определять, сколько каких действий за единицу времени может выполнить человек. Скажем, вновь зарегистрированный участник может послать не более 10 личных сообщений в час разным людям и не более 5 запросов в друзья (при этом будет так же дополнительный суточный лимит, например, 100 сообщений в сутки и 20 запросов в друзья), а пользователь с высоким рейтингом -- гораздо больше. Социальный Рейтинг будет немного расти по мере активного пребывания пользователя в системе, а так же по мере одобрения его действий другими пользователями, причем с учетом их собственного рейтинга. Негативная реакция будет этот самый социальный рейтинг понижать (точнее, при совершении отрицальных действий, т.е. удалении сообщения, отказе принять в друзья будет два варианта: просто отказ и отказ с понижением соц. рейтинга, за исключением занесения в черный список -- за это будут понижать всегда).&lt;br /&gt;Таким образом, в итоге должен получиться с одной стороны, удобный инструмент для отслеживания нужного и интересного для каждого пользователя, с другой -- некий коллективный разум, отбирающий и рекомендующий только то, что реально полезно многим людям. Кстати, вот и неплохое название для соц. сети: &quot;Коллективный разум&quot;! Жаль только, что сам я такое не смогу ни реализовать, ни тем более раскрутить.&lt;/p&gt;&lt;div class=&quot;item_footer&quot;&gt;&lt;p&gt;&lt;small&gt;&lt;a href=&quot;http://new.xpro.su/ideas/social-network&quot;&gt;Original post&lt;/a&gt; blogged on &lt;a href=&quot;http://b2evolution.net/&quot;&gt;b2evolution&lt;/a&gt;.&lt;/small&gt;&lt;/p&gt;&lt;/div&gt;</description>
			<content:encoded><![CDATA[<p>Идея социальных сетей в том виде, в котором они есть сейчас, начинает понемногу исчерпывать себя. И одна из причин этого -- обязательное требование указания реального имени, что противоречит самой идее Интернета, в котором нормой является анонимное общение. На начальном этапе такое требование можно было понять: социальные сети были ориентированы на то, чтобы дать людям, давно потерявшим друг друга, восстановить связь между собой, и использование реального имени значительно упрощало эту задачу. Но сейчас это уже не столь актуально: большинство тех, кто хотел найти друг друга, уже нашли.<br />Таким образом, прежняя задача социальных сетей выполнена, и уже сейчас наметилась новая: профиль социальной сети для человека становится местом, с которого начинается его субъективный Интернет, перекрестком, который связывает в единое целое его активность на различных сайтах, личным "аналитическим центром", который собирает для данного человека информацию в соответствии с его интересами и кругом общения. Я решил написать небольшой обзор того, какой эту сеть будущего вижу я.<br />Что же для этого нужно? Во-первых, разрешить анонимную регистрацию в социальных сетях. Во-вторых, еще более усилить интеграцию между самой социальной сетью и сторонними сайтами. Уже сейчас на многие сайты можно войти без дополнительной регистрации, но если пользователь успел уже создать отдельный профиль, то входить через соцсеть он не будет, чтобы не возникало неоднозначности. Отсюда вывод: нужна возможность динамического преобразования профилей, причем в обе стороны (т.е. человек может и привязать свой профиль на сайте к соц. сети и в любой момент отвязать его). Кроме того, регистрация через соц. сеть должна предусматривать автоматическую передачу (а возможно, и синхронизацию) полей "О себе" и "Интересы". <br />В-третьих, основнополагающей идеей этой социальной сети будет "человек -- это то, с чем он себя ассоциирует". Это позволит решить проблему поиска человека в условиях анонимной сети. Поиск будет осуществляться, во-первых, через контакты (однозначно должен быть поиск по ICQ, Email, Jabber, Skype), а во-вторых, у человека должна быть возможность указать все сайты, которые он ассоциирует с собой и свои имена на них (в принципе, ВКонтакте уже пытались сделать что-то аналогичное, добавив возможность указывать форум и свое Сетевое имя на нем, но почему-то можно было указать только один), и найти его можно будет указав форумное имя и адрес сайта (если, конечно, он сочтет нужным указать его в настройках). <br />В-четвертых, нужна более тесная интеграция обсуждений. Т.е. оставив комментарий на каком-то форуме, человек может отметить его у себя в профиле соц.сети и видеть ответы других пользователей (но при этом вовсе не обязательно тащить это на публично видимую стену, как это делается ВКонтакте). Причем на самом форуме этот комментарий будет выглядеть как обычное сообщение, а не размещенное именно через социальную сеть, как это делается сейчас (хотя не очень представляю, как это реализовать технически, разве что через экспорт сообщений через RSS, начиная с определенного).<br />В-пятых, для любого сайта, к которому проявил какой-либо интерес хоть один из участников соц. сети будет создаваться своя страничка, на которой могут быть отзывы об этом сайте, предложения по доделке с сортировкой их по степени значимости (видел такой отдельный сервис, но не помню, как называется) и оценка его полезности участниками. При этом администрация сайта при желании сможет взять частичный контроль над этой страницей (например, настроить ее внешний вид и организовать автоматизированную выдачу туда новостей с сайта или информации о предстоящих событиях, на которые смогут подписываться заинтересованные пользователи), но не сможет эти отзывы стирать. Таким образом, получаем социальную сеть не только людей, но и сайтов. (Нечто подобное пытались сделать на TooDoo пару лет назад, но во-первых, сайты добавлялись только вручную, а во-вторых, просто не смогли раскрутиться.)<br />В-шестых, в такой социальной сети будет возможность настроить автоматическое отслеживание появления новых сайтов, событий и сообществ на интересующие пользователя темы. При этом так же будет интеллектуальная сортировка значимости этих событий (на основе рейтинга и количества времени, уделяемого источнику этих событий самим пользователем и его кругом общения) с возможностью выставить ограничение на количество предлагаемых событий/новостей.<br />В-седьмых, можно будет указвать свои предпочтения в плане книг, фильмов, компьютерных игр, и с одной стороны, использовать их при поиске людей, с другой -- получать на основании них рекомендации (аналогично сервису IMHO.NET). <br />Вся информация о событиях, комментариях, новостях и т.п. будет сводиться на одну страницу с удобным настраиваемым интерфейсом (что-то вроде главной страницы залогиненного Google-пользователя), так же будет предусмотрен календарь/планировщик для планирования собственного времени и дел. Будет возможность временно убирать ненужные события и сортировать их (и сообщества, их производящие) по категориям, показывая только часть из них, а так же, например, добровольно блокировать часть возможностей сети на определенный период (например, отключать личные сообщения от категории "Друзья" на время работы или закрывать доступ в сообщества). Пользователи тоже будут сортироваться по категориям, но при этом эта сортировка будет видна только владельцу профиля (а не как сейчас в G+ и Контакте).<br />Возможно, не лишней будет интеграция с платежной системой (с инфраструктурой, аналогичной WM, т.е. добровольная аттестация, арбитраж, BL и т.п.), хотя возможно, это лишнее и достаточно просто предусмотреть в WM возможность дать ссылку на эту соц. сеть.<br />Одной из основных проблем в данной сети будет спам и просто информационный шум (т.е. когда добавляются в друзья и начинают слать разную ерунду). Борьба с этим будет вестись с помощью Социального Рейтинга, который будет определять, сколько каких действий за единицу времени может выполнить человек. Скажем, вновь зарегистрированный участник может послать не более 10 личных сообщений в час разным людям и не более 5 запросов в друзья (при этом будет так же дополнительный суточный лимит, например, 100 сообщений в сутки и 20 запросов в друзья), а пользователь с высоким рейтингом -- гораздо больше. Социальный Рейтинг будет немного расти по мере активного пребывания пользователя в системе, а так же по мере одобрения его действий другими пользователями, причем с учетом их собственного рейтинга. Негативная реакция будет этот самый социальный рейтинг понижать (точнее, при совершении отрицальных действий, т.е. удалении сообщения, отказе принять в друзья будет два варианта: просто отказ и отказ с понижением соц. рейтинга, за исключением занесения в черный список -- за это будут понижать всегда).<br />Таким образом, в итоге должен получиться с одной стороны, удобный инструмент для отслеживания нужного и интересного для каждого пользователя, с другой -- некий коллективный разум, отбирающий и рекомендующий только то, что реально полезно многим людям. Кстати, вот и неплохое название для соц. сети: "Коллективный разум"! Жаль только, что сам я такое не смогу ни реализовать, ни тем более раскрутить.</p><div class="item_footer"><p><small><a href="http://new.xpro.su/ideas/social-network">Original post</a> blogged on <a href="http://b2evolution.net/">b2evolution</a>.</small></p></div>]]></content:encoded>
								<comments>http://new.xpro.su/ideas/social-network#comments</comments>
			<wfw:commentRss>http://new.xpro.su/?tempskin=_rss2&#38;disp=comments&#38;p=98</wfw:commentRss>
		</item>
				<item>
			<title>Безопасная загрузка файлов на сервер</title>
			<link>http://new.xpro.su/php/file-uploads</link>
			<pubDate>Thu, 25 Aug 2011 23:59:00 +0000</pubDate>			<dc:creator>4X_Pro</dc:creator>
			<category domain="main">Web-программирование</category>			<guid isPermaLink="false">97@http://new.xpro.su/</guid>
						<description>&lt;p&gt;Когда на сайте требуется разрешить пользователю загружать свои файлы (например, фото или аватары) с последующим их хранением на сервере, сразу возникает ряд проблем с безопасностью.&lt;/p&gt;
&lt;p&gt;Первая и самая очевидная — это &lt;strong&gt;имена файлов&lt;/strong&gt;. Их обязательно нужно проверять на спецсимволы, так как пользователь может подделать HTTP-запрос, в результате чего загружаемый файл будет иметь имя, например, ../index.php.  и при попытке его сохранения затрет корневой индекс. Кроме того, название может содержать русские буквы в кодировке windows-1251 или koi-8, которые некорректно сохранятся в файловой системе. Вывод: нужно сохранять файл не под тем именем, под которым его загрузил пользователь, а под случайным, например MD5-хешем от имени файла, времени загрузки и IP пользователя. Имя же этого файла где-нибудь в базе, а потом отдавать файл скритом, который предварительно будет выдавать заголовок Content-disposition: attachment; filename=&quot;имя_файла&quot;.&lt;/p&gt;
&lt;p&gt;Вторая проблема — &lt;strong&gt;нельзя доверять расширению или MIME-типу&lt;/strong&gt;, их при желании можно подделать. Поэтому если тип файла важен, его нужно проверять на соответствие формату уже на сервере (для картинок хорошо подойдет функция getimagesize из модуля GD, для других типов — чтение заголовков) и отвергать те файлы, у которых формат не соответствует.&lt;/p&gt;
&lt;p&gt;И наконец, третья, самая главная проблема — &lt;strong&gt;загрузка исполняемых скриптов&lt;/strong&gt;. Тут есть несколько решений. Первое — если предполагается загрузка только определенных видов файлов (например, автар может быть только картинкой в PNG, JPG, GIF) и отвергать все, что не подходит. Но иногда требуется разрешить загружать файлы любых типов. Тогда возникает второй вариант: проверять расширение загруженного файла и, если оно небезопасно, переименовывать его (например, замена расширения с .php на .phps приведет к тому, что скрипт не будет выполняться, а будет показан его код с подсветкой синтаксиса). Главный недостаток этого решения -- может оказаться, что на каком-то сервере настроено исполнение скриптов с необычном расширением, например, .php3, и отфильтровать это не получится. И наконец, третий вариант -- это отключить обработку скриптов, например через .htaccess:&lt;/p&gt;
&lt;pre&gt;RemoveHandler .phtml .php .php3 .php4 .php5 .php6 .phps .cgi .exe .pl .asp .aspx .shtml .shtm .fcgi .fpl .jsp .htm .html .wml&lt;br /&gt;AddType application/x-httpd-php-source .phtml .php .php3 .php4 .php5 .php6 .phps .cgi .exe .pl .asp .aspx .shtml .shtm .fcgi .fpl .jsp&lt;br /&gt;&lt;/pre&gt;
&lt;p&gt;Однако следует учесть, что файл .htaccess влияет только на Apache (да и то его использование нужно включить в настройках), а на других серверах он будет проигнорирован. (Особенно важно это для скриптов, которые выкладываются в публичный доступ: рано или поздно найдется пользователь с каким-нибудь IIS, который не примет должных мер, поэтому лучше сочетать этот способ с предыдущим.)&lt;/p&gt;
&lt;p&gt;И последнее: после прочтения этого текста может возникнуть желание хранить пользовательские файлы вообще в базе данных. Не стоит этого делать: хотя это и кажется простым решением, но следует учитывать, что современные поисковики индексируют не только обычные HTML-страницы, но и файлы других типов. И в момент прохождения поискового робота нагрузка на SQL-сервер будет резко возрастать из-за необходимости отдать сразу большой объем данных, что будет приводить к проблемам в работе сайта.&lt;/p&gt;&lt;div class=&quot;item_footer&quot;&gt;&lt;p&gt;&lt;small&gt;&lt;a href=&quot;http://new.xpro.su/php/file-uploads&quot;&gt;Original post&lt;/a&gt; blogged on &lt;a href=&quot;http://b2evolution.net/&quot;&gt;b2evolution&lt;/a&gt;.&lt;/small&gt;&lt;/p&gt;&lt;/div&gt;</description>
			<content:encoded><![CDATA[<p>Когда на сайте требуется разрешить пользователю загружать свои файлы (например, фото или аватары) с последующим их хранением на сервере, сразу возникает ряд проблем с безопасностью.</p>
<p>Первая и самая очевидная — это <strong>имена файлов</strong>. Их обязательно нужно проверять на спецсимволы, так как пользователь может подделать HTTP-запрос, в результате чего загружаемый файл будет иметь имя, например, ../index.php.  и при попытке его сохранения затрет корневой индекс. Кроме того, название может содержать русские буквы в кодировке windows-1251 или koi-8, которые некорректно сохранятся в файловой системе. Вывод: нужно сохранять файл не под тем именем, под которым его загрузил пользователь, а под случайным, например MD5-хешем от имени файла, времени загрузки и IP пользователя. Имя же этого файла где-нибудь в базе, а потом отдавать файл скритом, который предварительно будет выдавать заголовок Content-disposition: attachment; filename="имя_файла".</p>
<p>Вторая проблема — <strong>нельзя доверять расширению или MIME-типу</strong>, их при желании можно подделать. Поэтому если тип файла важен, его нужно проверять на соответствие формату уже на сервере (для картинок хорошо подойдет функция getimagesize из модуля GD, для других типов — чтение заголовков) и отвергать те файлы, у которых формат не соответствует.</p>
<p>И наконец, третья, самая главная проблема — <strong>загрузка исполняемых скриптов</strong>. Тут есть несколько решений. Первое — если предполагается загрузка только определенных видов файлов (например, автар может быть только картинкой в PNG, JPG, GIF) и отвергать все, что не подходит. Но иногда требуется разрешить загружать файлы любых типов. Тогда возникает второй вариант: проверять расширение загруженного файла и, если оно небезопасно, переименовывать его (например, замена расширения с .php на .phps приведет к тому, что скрипт не будет выполняться, а будет показан его код с подсветкой синтаксиса). Главный недостаток этого решения -- может оказаться, что на каком-то сервере настроено исполнение скриптов с необычном расширением, например, .php3, и отфильтровать это не получится. И наконец, третий вариант -- это отключить обработку скриптов, например через .htaccess:</p>
<pre>RemoveHandler .phtml .php .php3 .php4 .php5 .php6 .phps .cgi .exe .pl .asp .aspx .shtml .shtm .fcgi .fpl .jsp .htm .html .wml<br />AddType application/x-httpd-php-source .phtml .php .php3 .php4 .php5 .php6 .phps .cgi .exe .pl .asp .aspx .shtml .shtm .fcgi .fpl .jsp<br /></pre>
<p>Однако следует учесть, что файл .htaccess влияет только на Apache (да и то его использование нужно включить в настройках), а на других серверах он будет проигнорирован. (Особенно важно это для скриптов, которые выкладываются в публичный доступ: рано или поздно найдется пользователь с каким-нибудь IIS, который не примет должных мер, поэтому лучше сочетать этот способ с предыдущим.)</p>
<p>И последнее: после прочтения этого текста может возникнуть желание хранить пользовательские файлы вообще в базе данных. Не стоит этого делать: хотя это и кажется простым решением, но следует учитывать, что современные поисковики индексируют не только обычные HTML-страницы, но и файлы других типов. И в момент прохождения поискового робота нагрузка на SQL-сервер будет резко возрастать из-за необходимости отдать сразу большой объем данных, что будет приводить к проблемам в работе сайта.</p><div class="item_footer"><p><small><a href="http://new.xpro.su/php/file-uploads">Original post</a> blogged on <a href="http://b2evolution.net/">b2evolution</a>.</small></p></div>]]></content:encoded>
								<comments>http://new.xpro.su/php/file-uploads#comments</comments>
			<wfw:commentRss>http://new.xpro.su/?tempskin=_rss2&#38;disp=comments&#38;p=97</wfw:commentRss>
		</item>
				<item>
			<title>Впечатления от Mozilla Firefox 6</title>
			<link>http://new.xpro.su/software/firefox-6</link>
			<pubDate>Thu, 25 Aug 2011 10:11:00 +0000</pubDate>			<dc:creator>4X_Pro</dc:creator>
			<category domain="main">Полезный софт</category>			<guid isPermaLink="false">96@http://new.xpro.su/</guid>
						<description>&lt;p&gt;Сегодня, запустив Firefox, с удивлением узнал, что уже вышла шестая версия. Скачал обновление, запустил, и… понравилось настолько, что сейчас даже подумываю о том, чтобы снова начать использовать Firefox как основной броузер. (Примечание: до этого у меня стояла версия 3.5.x, поэтому не исключаю, что часть описанных ниже изменений появилась в более ранних версиях.)&lt;/p&gt;
&lt;p&gt;Понравилось вот что:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Минималистичный интерфейс, теперь почти такой же, как у Google Chrome.&lt;/li&gt;
&lt;li&gt;Быстрая загрузка и отрисовка страниц. Сайт (если только не тормозит сервер) отрисовывается буквально сразу, а по мере догрузки идут незначительные изменения во внешнем виде.&lt;/li&gt;
&lt;li&gt;Ускорена работа с JavaScript. В частности, GMail открывается теперь так же, как в Google Chrome, если даже не быстрее. Так же быстрее грузятся визуальные редакторы типа TinyMCE.&lt;/li&gt;
&lt;li&gt;Управление группами вкладок! Совершенно необходимая для меня возможность, так как если у меня открыто более 6 вкладок, я начинаю путаться и вместо работы бессмысленно переключаться между ними. Теперь же можно все ненужные вкладки вынести в отдельные группы, и мешать работать они не будут.&lt;/li&gt;
&lt;li&gt;В Windows 7 — показ previews всех вкладок при наведении мышкой на значок в Панель задач. Очень удобно для того, чтобы переключиться сразу в нужную вкладку.&lt;/li&gt;
&lt;li&gt;Встроенные средства Web-разработки, в частности, отслеживание запросов сразу, без дополнительных расширений. Тоже весьма полезно.&lt;/li&gt;
&lt;li&gt;Так же возникло впечатление, что улучшено сглаживание шрифтов, по крайней мере, они выглядят лучше, чем в Chrome.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Ну и кроме того, под Firefox есть такие полезные дополнения, как It&#039;s all text (редактирование текста во внешнем редакторе) и Вебмастер SAPE, аналогов которым для других броузеров я так и не нашел.&lt;/p&gt;
&lt;p&gt;Из недостатков можно отметить только два традиционных: большой объем потребляемой памяти (при 4 открытых вкладках после часа использования -- 360 Mb на 64-битной системе) и необходимость рестарта после установки/удаления дополнения.&lt;/p&gt;&lt;div class=&quot;item_footer&quot;&gt;&lt;p&gt;&lt;small&gt;&lt;a href=&quot;http://new.xpro.su/software/firefox-6&quot;&gt;Original post&lt;/a&gt; blogged on &lt;a href=&quot;http://b2evolution.net/&quot;&gt;b2evolution&lt;/a&gt;.&lt;/small&gt;&lt;/p&gt;&lt;/div&gt;</description>
			<content:encoded><![CDATA[<p>Сегодня, запустив Firefox, с удивлением узнал, что уже вышла шестая версия. Скачал обновление, запустил, и… понравилось настолько, что сейчас даже подумываю о том, чтобы снова начать использовать Firefox как основной броузер. (Примечание: до этого у меня стояла версия 3.5.x, поэтому не исключаю, что часть описанных ниже изменений появилась в более ранних версиях.)</p>
<p>Понравилось вот что:</p>
<ol>
<li>Минималистичный интерфейс, теперь почти такой же, как у Google Chrome.</li>
<li>Быстрая загрузка и отрисовка страниц. Сайт (если только не тормозит сервер) отрисовывается буквально сразу, а по мере догрузки идут незначительные изменения во внешнем виде.</li>
<li>Ускорена работа с JavaScript. В частности, GMail открывается теперь так же, как в Google Chrome, если даже не быстрее. Так же быстрее грузятся визуальные редакторы типа TinyMCE.</li>
<li>Управление группами вкладок! Совершенно необходимая для меня возможность, так как если у меня открыто более 6 вкладок, я начинаю путаться и вместо работы бессмысленно переключаться между ними. Теперь же можно все ненужные вкладки вынести в отдельные группы, и мешать работать они не будут.</li>
<li>В Windows 7 — показ previews всех вкладок при наведении мышкой на значок в Панель задач. Очень удобно для того, чтобы переключиться сразу в нужную вкладку.</li>
<li>Встроенные средства Web-разработки, в частности, отслеживание запросов сразу, без дополнительных расширений. Тоже весьма полезно.</li>
<li>Так же возникло впечатление, что улучшено сглаживание шрифтов, по крайней мере, они выглядят лучше, чем в Chrome.</li>
</ol>
<p>Ну и кроме того, под Firefox есть такие полезные дополнения, как It's all text (редактирование текста во внешнем редакторе) и Вебмастер SAPE, аналогов которым для других броузеров я так и не нашел.</p>
<p>Из недостатков можно отметить только два традиционных: большой объем потребляемой памяти (при 4 открытых вкладках после часа использования -- 360 Mb на 64-битной системе) и необходимость рестарта после установки/удаления дополнения.</p><div class="item_footer"><p><small><a href="http://new.xpro.su/software/firefox-6">Original post</a> blogged on <a href="http://b2evolution.net/">b2evolution</a>.</small></p></div>]]></content:encoded>
								<comments>http://new.xpro.su/software/firefox-6#comments</comments>
			<wfw:commentRss>http://new.xpro.su/?tempskin=_rss2&#38;disp=comments&#38;p=96</wfw:commentRss>
		</item>
				<item>
			<title>Как определить, запущен ли PHP из командной строки</title>
			<link>http://new.xpro.su/php/php-cli-test</link>
			<pubDate>Mon, 15 Aug 2011 22:12:00 +0000</pubDate>			<dc:creator>4X_Pro</dc:creator>
			<category domain="main">Web-программирование</category>			<guid isPermaLink="false">95@http://new.xpro.su/</guid>
						<description>&lt;p&gt;Иногда при написании скриптов, предназначенных для выполнения длительных операций, возникает необходимость определить, каким именно способом запущен PHP-интерпретатор: из командной строки или же из Web-сервера. Помочь в этом может функция &lt;span style=&quot;font-family: &#039;courier new&#039;, courier;&quot;&gt;php_sapi_name()&lt;/span&gt;, которая возвращает строку, описывающую SAPI (подсистему сервера), которая использовалась.&lt;/p&gt;
&lt;p&gt;При запуске из командной строки функция возвращает значение &quot;&lt;span style=&quot;font-family: &#039;courier new&#039;, courier;&quot;&gt;cli&lt;/span&gt;&quot;, при запуске в качестве CGI-модуля &quot;&lt;span style=&quot;font-family: &#039;courier new&#039;, courier;&quot;&gt;cgi&lt;/span&gt;&quot; или &quot;&lt;span style=&quot;font-family: &#039;courier new&#039;, courier;&quot;&gt;fast-cgi&lt;/span&gt;&quot;, а если как модуль сервера -- то имя этого сервера.&lt;/p&gt;
&lt;p&gt;Важно отметить, что если из командной строки скрипт запускается не с помощью php, а с помощью php-cgi, то и функция будет возвращать cgi, а так же будут применены ограничения по времени и памяти, заданные в php.ini (&lt;span style=&quot;font-family: &#039;courier new&#039;, courier;&quot;&gt;memory_limit&lt;/span&gt; и &lt;span style=&quot;font-family: &#039;courier new&#039;, courier;&quot;&gt;max_execution_time&lt;/span&gt;).&lt;/p&gt;
&lt;p&gt;Пример кода для проверки:&lt;/p&gt;
&lt;pre&gt;$sapi = php_sapi_name();&lt;br /&gt;if ($sapi==&#039;cli&#039;) echo &#039;Запуск из командной строки&#039;;&lt;br /&gt;elseif (substr($sapi,0,3)==&#039;cgi&#039;) echo &#039;Запуск в режиме CGI&#039;;&lt;br /&gt;elseif (substr($sapi,0,6)==&#039;apache&#039;) echo &#039;Запуск в режиме модуля Apache&#039;;&lt;br /&gt;else echo &#039;Запуск в режиме модуля сервера &#039;.$sapi;&lt;/pre&gt;&lt;div class=&quot;item_footer&quot;&gt;&lt;p&gt;&lt;small&gt;&lt;a href=&quot;http://new.xpro.su/php/php-cli-test&quot;&gt;Original post&lt;/a&gt; blogged on &lt;a href=&quot;http://b2evolution.net/&quot;&gt;b2evolution&lt;/a&gt;.&lt;/small&gt;&lt;/p&gt;&lt;/div&gt;</description>
			<content:encoded><![CDATA[<p>Иногда при написании скриптов, предназначенных для выполнения длительных операций, возникает необходимость определить, каким именно способом запущен PHP-интерпретатор: из командной строки или же из Web-сервера. Помочь в этом может функция <span style="font-family: 'courier new', courier;">php_sapi_name()</span>, которая возвращает строку, описывающую SAPI (подсистему сервера), которая использовалась.</p>
<p>При запуске из командной строки функция возвращает значение "<span style="font-family: 'courier new', courier;">cli</span>", при запуске в качестве CGI-модуля "<span style="font-family: 'courier new', courier;">cgi</span>" или "<span style="font-family: 'courier new', courier;">fast-cgi</span>", а если как модуль сервера -- то имя этого сервера.</p>
<p>Важно отметить, что если из командной строки скрипт запускается не с помощью php, а с помощью php-cgi, то и функция будет возвращать cgi, а так же будут применены ограничения по времени и памяти, заданные в php.ini (<span style="font-family: 'courier new', courier;">memory_limit</span> и <span style="font-family: 'courier new', courier;">max_execution_time</span>).</p>
<p>Пример кода для проверки:</p>
<pre>$sapi = php_sapi_name();<br />if ($sapi=='cli') echo 'Запуск из командной строки';<br />elseif (substr($sapi,0,3)=='cgi') echo 'Запуск в режиме CGI';<br />elseif (substr($sapi,0,6)=='apache') echo 'Запуск в режиме модуля Apache';<br />else echo 'Запуск в режиме модуля сервера '.$sapi;</pre><div class="item_footer"><p><small><a href="http://new.xpro.su/php/php-cli-test">Original post</a> blogged on <a href="http://b2evolution.net/">b2evolution</a>.</small></p></div>]]></content:encoded>
								<comments>http://new.xpro.su/php/php-cli-test#comments</comments>
			<wfw:commentRss>http://new.xpro.su/?tempskin=_rss2&#38;disp=comments&#38;p=95</wfw:commentRss>
		</item>
			</channel>
</rss>

