Статьи

Как создать веб-сервис: начало до конца

Там нет двух способов об этом; собрать сайт сложно! Вам нужно знать множество языков программирования (HTML, PHP, CSS, AJAX, Javascript, MySQL и другие). Вы также должны понимать, как объединить эти инструменты в одно связное целое, которое, как мы надеемся, больше, чем сумма его частей. В наши дни мы используем фреймворки, шаблоны и генераторы, но знаете ли вы, когда вы к этому приступаете, с нуля?

Это руководство предназначено для тех, кто может написать функцию PHP, знает, что такое jQuery, может хорошо справляться со своим CSS, но хотел бы получить инсайдерскую информацию о том, как веб-сервис создается с нуля. Как мне организовать свои файлы, где хранить свои функции, как начать планировать, как подключаться к базе данных, как обрабатывать вызовы AJAX и как управлять своими 404 страницами — это только некоторые вопросы Я пытаюсь пролить свет на это здесь.


Прежде чем мы начнем, быстрое заявление об отказе от ответственности: пока мы создаем работающий сервис, он не принимает огромных мер для обеспечения безопасности и масштабируемости. Основные вещи, такие как шифрование пароля, правила .htaccess, проверка разрешений включены, но вы можете и должны сделать гораздо больше, если хотите создать что-то для массового потребления.

Приложение так далеко от совершенства, но я подумала, что было бы освежающим изменением темпа показать вам процесс «Как я доберусь и планирую продолжить» вместо «Вот что-то потрясающее, что я разработала». Веб-разработка — это не работа с одним циклом, когда вы создаете что-то замечательное на шаге 1, а деньги вкладывают на шаге 2 (хотя это было бы здорово). Обычно вы перебираете версии проекта, придя к чему-то уникальному и замечательному.

Я надеюсь продемонстрировать основную логику некоторых методов, которые вы можете использовать, чтобы вы могли решить, как делать что-то. Я попытался указать на ошибки и плохие практики (некоторые из которых я использовал) по пути. Я призываю вас подвергнуть сомнению как можно больше шагов и подумать о лучших способах сделать что-то, возможно, даже отбросив свои собственные идеи через комментарии.


Сервис, который мы будем создавать, называется Bonsai Writer, инструментом, который преобразует простой текст в HTML. Он предназначен в основном для писателей, но также может использоваться как хранилище для заметок для более общего использования.

Лично я пишу статьи для многих журналов, у каждого из которых есть различные руководящие принципы моделирования. Однако когда я пишу, я хочу сосредоточиться на написании, а не на стилизации. Идея Bonsai Writer зародилась, как инструмент для легкого управления своими статьями.


Планирование должно быть самым первым делом, которое вы делаете, и должно занять довольно много времени. Я построил Bonsai Writer по прихоти, планируя всего 20-30 минут, и это видно! Это работает и выглядит удовлетворительно, но с несколькими дополнительными часами это могло бы быть намного лучше!

Первым шагом в любом проекте должно быть планирование различных этапов, через которые прошел бы ваш проект. Это может быть так же сложно, как диаграмма Ганта, охватывающая несколько ресурсов и подпроектов, но это может быть просто список и несколько слов о том, что вы хотите сделать. Я предлагаю планирование пропорционально вашей заявке. Если вы осуществляете шестимесячный проект, вам будет полезно создать некоторые документы, таблицы и другие инструменты, которые помогут вам. Однако для такого простого проекта достаточно простого списка целей.

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

В качестве первого шага я нарисовал на своей доске два довольно простых каркаса: один изображал главную страницу, другой — само приложение. Я настоятельно рекомендую доски в качестве инструментов планирования. У меня на стене доска размером 5х2,5 фута, и она мне очень помогает на этапах планирования.

Некоторые основные каркасы, чтобы указать мне в правильном направлении.

Как видите, все это довольно просто, но это дало мне твердую точку для продолжения.

Если вы разрабатываете приложение для широкого использования, я советую тратить гораздо больше времени на создание каркасов. Я обнаружил, что чем больше у вас каркаса, тем быстрее вы будете создавать свой сайт. Таким образом, даже если вы не кодируете, вы все еще на пути. Хороший каркас может содержать больше представлений, показывающих, что происходит, когда вы нажимаете важную ссылку, некоторый текст, объясняющий, на что мы смотрим, и так далее. Если вы хотите узнать больше о каркасном дизайне, я предлагаю взглянуть на журнал Wireframes .

При работе над проектом мне нравится следить за каркасным дизайном и создавать ключевые кадры. Веб-сайт действительно имеет только три просмотра, поэтому я создал элементарные дизайны главной страницы и страницы приложения. У меня в голове была твердая идея об ошибке, поэтому я решил пропустить это. Это не очень хорошая практика. Сводка вещей на «бумаге» может быть огромной помощью. Помимо того, что вы можете поделиться ею, это также помогает предоставить вам больше перспективы. Я много раз держал в голове идею дизайна целую вечность, думая, что это было Круто, тогда, однажды нарисованная, это выглядело действительно ужасно через несколько дней.

Полученные конструкции

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

Сам процесс проектирования был довольно прост. Я знал, что хочу красивое дерево бонсай, поэтому я начал с того, что нашел красивое изображение на Фотодуне . Я нашел отличное изображение Maple Bonsai и, после некоторой обрезки, обрезки и применения одного из фильтров, я создал дерево, которое вы сейчас видите на сайте.

Я решил использовать пастельные тона, поэтому остановился на персиковом цвете, создавая некий монотонный дизайн. Совершенно очевидно, что я не дизайнер, но здесь все будет хорошо.

Итак, на основании вышеизложенного, что мы строим здесь? Это помогает мне думать о проекте с трех разных точек зрения.

  • На уровне базы данных. Нам понадобится таблица для хранения статей и пользователей, поэтому она может помочь придумать хорошую структуру базы данных.
  • С другой стороны, нам нужно будет иметь возможность извлекать и сохранять статьи, преобразовывать обычный текст в HTML и иметь возможность входить и выходить из пользователей.
  • Для внешнего интерфейса мы хотим, чтобы пользователи могли переключаться между представлениями (простой текст и HTML) с комбинациями клавиш; мы хотим, чтобы они могли показывать и скрывать меню нажатиями клавиш; нам нужен своего рода лайтбокс для некоторых всплывающих окон и некоторых других мелких вещей.

Если вы хотите углубиться в детали, вы можете составить схему базы данных, написать некоторые функции скелета, решить, какую кодировку пароля использовать, как входить и выходить из системы (например, сеансы или файлы cookie), решить, хотите ли вы использовать JS-фреймворк, такой как JQuery и так далее. Вы можете заранее собрать все необходимые внешние ресурсы для быстрой и простой реализации.

Со своей стороны, я также сразу установил систему контроля версий SVN вместе с системой отслеживания ошибок Redmine . Я планирую сделать это приложение намного лучше в будущем, поэтому полезными инструментами будут контроль версий и отслеживание ошибок. Я считаю, что оба очень полезны для личных или не совместных проектов. Контроль версий дает вам большую историю разработки (не говоря уже о резервном копировании), а средство отслеживания ошибок — отличное место для размещения ошибок, которые вы хотите исправить, идей, которые у вас есть, и других заметок.


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

  • Использование localhost на вашем компьютере и использование подкаталога (например: http: // localhost / bonsaiwriter)
  • Использование localhost и виртуального хоста (например: http: // bonsaiwriter)
  • Использование удаленного хоста с выделенным доменом (например: http://mybonsaiwriter.com)
  • Использование удаленного хоста с поддоменом (например: http://bonsaiwriter.mysite.com)
  • Использование удаленного хоста и подкаталога (например: http://mysite.com/bonsaiwriter)

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

Я начал с создания некоторых файлов, которые, как я знал, понадобятся, и поместил их в соответствующие каталоги. Этот шаг позволяет мне мысленно представить, как сайт будет структурирован, и визуализировать, как файлы включаются друг в друга во время работы сайта. Вот что я придумал после первого прохода.

  • .htaccess
  • index.php
  • config.php
  • load.php
  • Заголовок-app.php
  • Заголовок-site.php
  • Заголовок-error.php
  • сноска-app.php
  • сноска-site.php
  • сноска-error.php
  • 404.php
  • style.less
  • [DIR] приложение
  • index.php
  • [DIR] веб-сайт
  • index.php
  • [DIR] JS
  • [DIR] изображения
  • [DIR] включает в себя
  • functions.php
  • appFunctions.php

Идея состоит в том, что файл «точки входа» всегда является корневым файлом index.php. Используя правила .htaccess, все запросы отправляются в основной файл index.php, и этот файл определяет, что с ними делать. По сути это означает, что независимо от того, что вы вводите после «bonsaiwriter.com/», будет загружен основной файл index.php.

Чтобы быть уверенным в том, что мы всегда можем использовать наше соединение с базой данных, функцию и другие определенные кусочки, мы включаем load.php в каждый отдельный запрос, используя другое правило .htaccess. Этот загрузочный файл со значением pull в файле config.php, настройкой любых переменных, созданием необходимых нам функций и т. Д. — об этом позже.

В зависимости от страницы, на которой мы находимся, различные файлы будут включены через index.php. Если мы посмотрим на приложение, будет включен файл header-app.php, затем файл app / index.php, затем файл footer-app.php из основного каталога. Вы можете быть удивлены, почему файлы верхнего и нижнего колонтитула не находятся в соответствующих каталогах. Основной причиной является проверка будущего и простота использования. В действительности заголовок ошибки — это не просто заголовок для ошибок, это заголовок для всех тех страниц, которые структурированы как страница ошибок. Было бы более уместно назвать этот файл any-page-which-just-has-the-tree-and-some-text-header.php. В нашем случае это просто страница с ошибкой, но позже она может эволюционировать, поэтому файл будет храниться в корневом каталоге, чтобы впоследствии я мог легко включить его в любой файл.

Каталоги js и images пока пусты, но я думаю, что они говорят сами за себя. Мы поместим все наши js-файлы в каталог js, а все наши изображения — в каталог images — не совсем ракетостроение.

Каталог включает в себя любые функциональные файлы или сторонние файлы, которые мы хотим включить. Первый из них — functions.php — содержит общие функции, необходимые для работы самого сайта. Здесь можно найти функцию для отображения меню или создания выдержки. Файл appFunctions.php будет включать в себя любые специальные функции приложения для извлечения данных статьи, их сохранения и так далее.

Мы будем управлять внешним видом нашего сайта с помощью файла .less.

LESS — это динамический язык таблиц стилей, который позволяет нам добавить некоторую логику программирования в наш CSS и сделать работу со стилями намного более чистой.

Использование LESS потребует от нас также ссылки на файл Javascript, но это нормально, потому что это обеспечивает нам большую гибкость. В производственной среде я бы скомпилировал файл LESS в соответствующий файл CSS, уменьшил его и использовал бы его, но для этого проекта мы будем сохранять все как есть.

Плохо нарисованная схема работы загрузчика.

Сначала я хотел поставить логику загрузчика на место. Желаемый результат заключается в том, что при посещении корневого URL (в моем случае http://bonsaiwriter.com) происходят следующие вещи.

  • load.php запускается
  • config.php включен в этот файл сразу, чтобы настроить вещи
  • наши функции включены (functions.php и appFunctions.php)
  • соединение с базой данных установлено
  • index.php окончательно загружен

Чтобы начать работу, войдите в свой файл .htaccess и добавьте следующую строку:

1
php_value auto_prepend_file load.php

Если вы работаете в сети, в некоторых случаях ваш хост (например, Bluehost) не позволит вам включать файлы через .htaccess. Закройте и снова откройте файл .htaccess после попытки загрузить страницу, и вы должны увидеть, что строка автоматического предварения закомментирована. Если это произойдет, создайте файл php.ini в корневом каталоге и используйте в нем следующий код:

1
auto_prepend_file = «load.php»

Затем откройте load.php и введите «Привет». Откройте свой файл index.php и введите «, вы классные». Если вы загрузите веб-сайт в своем браузере, вы увидите сообщение «Привет, ты классный». Это проверяет, что index.php загружается и что к нему добавляется load.php. Далее, давайте настроим наш файл load.php, используя приведенный ниже код.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
<!—?php <br ?—>/**
 * The Website Loader File
 *
 * This file is responsible for loading the whole website.
 * It includes the necessary files, fires up the database connection
 * and so on.
 *
 * @author Daniel Pataki
 * @version 0.1 alpha
 * @package bonsaiwriter
 */
// Load the configuration file
include(«config.php»);
// Load functions
include(INCLUDES_PATH.»/functions.php»);
include(INCLUDES_PATH.»/appFunctions.php»);
// Create a database connection
// We will add this in soon
?>

Как видите, я добавил документацию в этот файл с использованием синтаксиса phpdoc . Это будет полезно не только для других разработчиков, но и для вас. Если вы вернетесь к нему через два месяца, чтобы исправить одну или две ошибки, вы не будете знать, что происходит дальше. Я опущу документацию по следующим примерам, но они есть в большинстве файлов; взгляните на пакет проекта для получения дополнительной информации.

В load.php первое, что мы должны сделать, это включить наш конфигурационный файл, а затем два функциональных файла. Я использовал константу для хранения пути, который определен в config.php (см. Следующий раздел). Последнее, что нужно сделать — это установить соединение с базой данных, к которому мы перейдем, как только наш конфигурационный файл будет на месте.

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

01
02
03
04
05
06
07
08
09
10
11
12
// Site paths and urls
define(«PATH», «/home6/tastique/public_html/bonsaiwriter»);
define(«SITE_PATH», PATH.»/site»);
define(«APP_PATH», PATH.»/app»);
define(«INCLUDES_PATH», PATH.»/includes»);
define(«URL», «http://bonsaiwriter.com»);
define(«AJAX_URL», URL.»/ajax.php»);
// Database Details
define(«DB_NAME», «bonsaiwriter»);
define(«DB_USER», «bonsaiwriter»);
define(«DB_PASS», «supersecretpass»);
define(«DB_HOST», «localhost»);

На этом этапе я не особо продумывал вещи, поэтому вполне может быть, что мы вообще не будем использовать APP_PATH, но в то время это казалось хорошей идеей — мы всегда можем удалить ее позже. Никогда не бойся рефакторинга.

Эти два блока (paths и db info) позволят вам поработать на локальном хосте, а затем перенести сайт на онлайн-хост за считанные секунды. Все, что вам нужно сделать, это изменить значения здесь.

Следующим этапом нашей подготовки является подключение к базе данных. Во-первых, давайте удостоверимся, что мы создали один. Если вы используете localhost, зайдите в PhpMyAdmin (обычно это http: // localhost / phpmyadmin или http: // localhost / phpMyAdmin) и создайте новую базу данных, введя ее имя (bonsaiwriter) в поле «Создать новую базу данных». Вернитесь на главный экран PhpMyAdmin и нажмите вкладку привилегий. Нажмите «Добавить нового пользователя» под таблицей и используйте «bonsaiwriter» для имени пользователя, «localhost» для хоста и «supersecretpass» для вашего пароля (хотя вы можете попробовать использовать более безопасный). Рядом с «глобальными привилегиями» нажмите «проверить все ссылки» и нажмите «Перейти». Вы создали базу данных и пользователя, который обладает всеми привилегиями, необходимыми для работы с базой данных.

Если вы находитесь в сети, вы не сможете создать базу данных и пользователя через PhpMyAdmin. Многие хосты ограничивают использование этого инструмента, и вам придется использовать выделенный раздел в cPanel или где-то еще для этого. Если ваш хост использует cPanel, я предлагаю использовать MySQL Database Wizard для создания базы данных и пользователя.

По завершении вернитесь в файл load.php и замените закомментированную строку «Мы скоро добавим это» следующим кодом:

1
$bwdb = mysqli_connect(DB_HOST, DB_USER, DB_PASS, DB_NAME);

Это создаст для нас новое соединение и позволит нам использовать созданный объект $ bwdb для нужд транзакций базы данных.

После того, как вы настроили базу данных, вы можете добавить в нее таблицы через PhpMyAdmin. Просто зайдите в вашу базу данных и запустите следующий SQL.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
CREATE TABLE IF NOT EXISTS `article` (
  `ID` int(11) NOT NULL AUTO_INCREMENT,
  `uri` varchar(5) CHARACTER SET latin1 COLLATE latin1_general_cs NOT NULL,
  `raw` text NOT NULL,
  `html` text NOT NULL,
  `title` varchar(120) NOT NULL DEFAULT ‘Untitled Article’,
  `user_id` int(11) NOT NULL,
  `date_created` datetime NOT NULL,
  `date_updated` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  `public` int(1) NOT NULL DEFAULT ‘1’,
  PRIMARY KEY (`ID`),
  UNIQUE KEY `uri` (`uri`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1 ;
CREATE TABLE IF NOT EXISTS `user` (
  `ID` int(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(18) NOT NULL,
  `password` char(40) NOT NULL,
  `email` varchar(255) NOT NULL,
  PRIMARY KEY (`ID`),
  UNIQUE KEY `username` (`username`,`email`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1 ;

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

Теперь все готово: когда вы загружаете сайт, все предварительно добавлено, определено, включено, подключено и так далее, но прежде чем мы продолжим, я хочу добавить магию .htaccess, чтобы убедиться, что независимо от того, какой URL введен, index.php загружен.

Причина этого в том, что я планирую, чтобы в каждой статье был такой URL: http://bonsaiwriter.com/8h3ef/ Поскольку я не хочу создавать каталоги и индексные файлы для всех миллиардов возможностей, я просто перешлю все запросы в один файл и разбираться с ними там. Результатом этого является то, что, если вы перейдете на http://bonsaiwriter.com/i2e23e/23e23/e23e2/23e23/, вы не получите страницу с ошибкой, будет показано содержимое index.php. Очевидно, нам нужно будет исправить это в тех случаях, когда страница явно не существует, но об этом чуть позже!

Чтобы убедиться, что все ваши запросы обрабатываются одним файлом, откройте ваш файл .htaccess и вставьте следующие строки под существующим правилом prepend.

1
2
3
4
5
6
Options -Indexes
RewriteEngine on
RewriteCond %{REQUEST_URI} ^
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.+)?$ /index.php [L]

Это где вещи могут немного отличаться в зависимости от вашей настройки. Если вы используете виртуальный хост, выделенный домен или поддомен, это должно работать. Если вы используете подкаталог, вам нужно будет изменить последнюю строку следующим образом:

1
RewriteRule ^(.+)?$ /subdirectory/index.php [L]

Как только это будет сделано, вы сможете набрать что-нибудь в строке URL после вашего базового домена, и все должно быть хорошо — вы должны увидеть, как загружается index.php.


Наш файл index.php будет нашим файлом маршрутизации. Он определит, что вы хотите увидеть, и покажет это вам. Он будет знать, что http://bonsaiwriter.com/about/ существует, а http://bonsaieriter.com/aboutus/ — нет. Он будет знать, что на http://bonsaiwriter.com/334Dr/ есть статья, и покажет вам приложение со статьей, однако обнаружит, что статьи на http://bonsaiwriter.com/wr4rD/ нет. , так что он покажет страницу с ошибкой.

Давайте сначала посмотрим на страницы, которые у нас появятся, а затем определим, как направлять туда пользователей. Вот быстрая разбивка:

  • Целевая страница (http://bonsaiwriter.com)
  • Контактная информация, о и юридических страницах (http://bonsaiwriter.com/contact/)
  • Создать новую статью (http://bonsaiwriter.com/new/)
  • Страницы статей (http://bonsaiwriter.com/r4J2w/)

Теперь все, что нам нужно сделать, это написать правила для этих случаев и убедиться, что пользователи отправляются в правильные места. Давайте начнем с захвата страницы, на которой мы находимся, и удаления частей URL, которые нам не нужны.

1
2
$page = str_replace(«?».$_SERVER[«QUERY_STRING»], «», $_SERVER[«REQUEST_URI»]);
$page = substr($page, 1, -1);

Приведенный выше код должен быть размещен в верхней части файла index.php и будет возвращать ту часть URL, которая нам нужна. Если вы зайдете на http://bonsaiwriter.com/about/, он вернется «около»; если вы перейдете по адресу http://bonsaiwriter.com/this/is/a/page/, он вернет страницу «this / is / a /».

Приведенный выше метод также необходимо немного изменить, если вы используете подкаталоги, но это легко сделать, удалив его из URL с помощью функции str_replace() или чего-то подобного.

Если переменная $page пуста, мы находимся на главной странице. Если его значение «новое», мы хотим создать новую статью. Если это контакт, о или законно мы хотим показать соответствующие страницы. Во всех остальных случаях мы проверим, существует ли статья по указанному URL. Если это так, то мы покажем статью. Если это не так, у нас есть ошибка 404, поэтому ошибка отображается. Давайте посмотрим на код, который делает все это.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
if(empty($page)) {
  include(SITE_PATH.»/index.php»);
  exit();
}
elseif($page == ‘new’) {
  $uri = generate_article_uri();
  $ID = insert_article($uri);
  header(«Location: «.URL.»/».$uri.»/»);
  exit();
}
elseif(in_array($page, $pages) AND file_exists(SITE_PATH.»/».$page.».php»)) {
  include(SITE_PATH.»/».$page.».php»);
  exit();
}
elseif(article_exists($page)) {
  include(APP_PATH.»/index.php»);
  exit();
}
else {
  include(PATH.»/404.php»);
  exit();
}

Во-первых, мы проверяем, является ли переменная $page пустой. Если это так, мы включаем index.php из папки нашего сайта, так как мы хотим показать первую страницу.

Если переменная $page «новая», мы хотим создать новую статью. Для этого мы генерируем уникальный идентификатор статьи, далее называемый uri. Затем мы вставляем новую статью в базу данных с этим URI и перенаправляем пользователя на страницу статьи. Мы обсудим эти конкретные функции ниже.

В следующем разделе рассматриваются наши предопределенные страницы. Я добавил простой массив в config.php, который содержит страницы, которые мы хотим иметь:

1
$pages = array(«contact», «about», «legal»);

Если текущая страница находится среди них, и файл для представления страницы существует (site / pagename.php), мы включаем этот файл, в противном случае мы переходим к следующему условию.

Если код попадает в следующий блок, мы определили следующее:

  • Мы не показываем главную страницу
  • Мы не хотим создавать новую статью
  • Мы не показываем предварительно определенную страницу

Все, что осталось, это либо страница статьи, либо у нас ошибка. В этом блоке мы проверяем, есть ли у нас статья с идентификатором uri, совпадающим с данными, содержащимися в нашей переменной $page . Мы используем отдельную функцию для того, что возвращает true если статья существует, и false если ее нет (подробнее об этом в ближайшее время). Если статья существует, мы включаем файл index.php из папки приложения.

Если ни одно из вышеперечисленных условий не было выполнено, остается только то, что это должна быть страница с ошибкой, поэтому мы отображаем файл 404.php.

После этого мы учли все возможности, которые могут возникнуть при просмотре нашего маленького веб-приложения. Мы определили набор страниц, которые можно просматривать, и сказали, что все остальные страницы являются страницами с ошибками.


Давайте продолжим, определив и написав некоторые функции, которые понадобятся нашему приложению. Я начал со следующих шести функций, три из которых мы уже использовали:

  • article_exists — Когда передается URI в качестве аргумента, эта функция сообщит нам, существует ли он
  • generate_article_uri — генерирует случайную и уникальную 5-символьную строку для идентификации статьи
  • insert_article — добавить статью в базу данных
  • get_article — извлекает отдельную статью из базы данных на основе ее URI
  • save_article — сохраняет детали статьи
  • ! convert_plain_to_html — метод преобразования необработанного текста в HTML

Функция очень проста. Мы передаем ему URI и определяем базу данных, чтобы убедиться, что статья с этим URI не существует.

01
02
03
04
05
06
07
08
09
10
function article_exists($uri) {
  global $bwdb;
  $results = $bwdb->query(«SELECT ID FROM article WHERE uri = ‘$uri’ «);
  if($results->num_rows == 0) {
    return false;
  }
  else {
    return true;
  }
}

Мы создаем набор результатов, который состоит из всех статей с данным URI. Если количество возвращаемых строк равно 0, то ничего страшного, статья не существует. В противном случае, по крайней мере, одна статья была найдена, поэтому мы должны вернуть true — статья существует.

Генерация уникального URI является неотъемлемой частью приложения. Всякий раз, когда кто-то создает новую статью, он должен иметь уникальный идентификатор, но мы также не хотим слишком длинные URL-адреса и не хотим тратить кучу времени на поиск уникального. Вот как это делает Bonsai Writer.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
function generate_article_uri() {
  global $bwdb;
  $allowed = «abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890»;
  $allowed = str_split($allowed);
  do {
    $characters = array();
    for($i=0; $i shuffle($allowed);
      $characters[] = $allowed[0];
    }
    $uri = implode($characters);
  }
  while (article_exists());
  return $uri;
}

Сначала мы определим все символы, которые мы позволим. Я позволил все заглавные и строчные буквы и все цифры. Это даст нам 42 к числу 42 вариаций — мы должны быть в безопасности на некоторое время.

Я дал все разрешенные символы в виде строки, а затем разделил их на array с помощью функции str_split() . Затем я создал объект, который будет выполняться пять раз, выбрав случайный член array разрешенных символов (перетасовывая его на каждом проходе и выбрав первое значение), добавив его в наш array выбранных символов. Затем я взрываю этот выбранный array чтобы получить окончательную строку.

Как вы можете видеть, все это в цикле do-while while. Это работает так же, как обычный цикл while, за исключением того, что loop выполняется хотя бы один раз, несмотря ни на что. В обычных циклах while сначала проверяется условие, и, если оно true , loop запускается один раз, а затем снова проверяется условие. В циклах do-while loop запускается один раз, только затем проверяется условие.

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

Это может показаться удивительным, но этот метод менее чем хорош, если у вас огромный объем сайта. 49 до степени 49 — это много, но давайте посмотрим, как это может быть проблемой с меньшим набором. Предположим, вы хотите назначить людям 4-значный уникальный пин-код. Это дает вам 10000 вариантов. Ваш сайт посещают 20 человек в день, поэтому на какое-то время 10 000 подойдет.

Через год будет взято около 7300 пин-кодов, так что у вас еще будет время для внедрения новой системы. Однако, поскольку 73% пин-кодов занято, ваш генератор случайных чисел будет выплевывать снятый пин-код 73% времени (по сути, 2 из 3 раз). Если я зашел на ваш сайт, системе может потребоваться несколько попыток, прежде чем он найдет уникальный пин-код. Это приравнивается к дополнительным запросам к базе данных.

Выбор правильной схемы здесь важен, и я думаю, что с 49 символами, доступными для 5 мест (это дает вам 6 с 83 нулями после опций), мы действительно на некоторое время в безопасности.

Первоначально, когда кто-то создает статью, у нее нет данных — только URI — поэтому вставить ее довольно просто:

1
2
3
4
5
function insert_article($uri) {
  global $bwdb;
     $bwdb->query(«INSERT INTO article (uri) VALUES (‘$uri’) «);
  return $bwdb->insert_id;
}

Я хочу упомянуть важную заметку о URI здесь. Поскольку мы специально допустили прописные и строчные буквы, «abcde» не должен совпадать с «Abcde». Хотя это очевидно на уровне PHP, мы должны убедиться, что поле uri в базе данных имеет сортировку с учетом регистра. В моем случае все данные получают «latin1_swedish_ci» в качестве параметров сортировки по умолчанию, которые не чувствительны к регистру — на что указывает «ci» в конце. Для поля uri я выбрал «latin_general_cs», чтобы убедиться, что мы можем правильно проверить uris.

При получении статьи мы просто хотим, чтобы все ее детали были доступны для работы, поэтому все, что нам нужно сделать, это запросить ее строку из базы данных.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
function get_article($uri = false) {
  global $bwdb;
  if($uri == false) {
    $uri = substr($_SERVER[«REQUEST_URI»], 1, -1);
  }
  $results = $bwdb->query(«SELECT * FROM article WHERE uri = ‘$uri’ «);
  $article = $results->fetch_object();
  if($article) {
    return $article;
  }
  else {
    return false;
  }
}

Передача этой функции URI не является обязательным. Причина этого заключается в том, что на отдельных страницах статьи у нас обычно есть URL в любом случае. Поэтому, если URI не указан, мы просто анализируем его по URL.

Затем мы получаем строку этой статьи, помещая все данные в переменную $article которую мы возвращаем для использования. Если статья не найдена, мы возвращаем false .

Первоначально я написал функцию, которая преобразует наш простой текст в HTML, а затем сохраняет статью. Впоследствии я понял, что какое-то разделение будет лучше здесь. Хорошей практикой является иметь одну функцию для одной вещи и для одной. Функция сохранения не должна отвечать за преобразование. Мы могли бы использовать функцию преобразования внутри сохраняющих функций, но если мы разделяем их, дело в том, что обе функции доступны по отдельности. К счастью, я вернулся, чтобы переписать статью, чтобы казалось, что именно так я планировал все это время.

1
2
3
4
5
6
7
8
function action_save_article() {
  global $bwdb;
        $title = «»;
        $raw = «»;
        $html = convert_plain_to_html($raw);
        $uri = $_POST[«uri»];
        $bwdb->query(«UPDATE article SET title = ‘$title’, raw = ‘$raw’, html = ‘$html’ WHERE uri = ‘$_POST[uri]’ «);
}

Единственное, что я знаю с уверенностью, — это то, что uri будет передан этой функции с помощью метода post и что переменная $html получит свои данные путем преобразования значения в переменную $raw . В любом случае, это пока только заполнитель; посмотрим, как пойдут дела.


Теперь, когда у нас есть некоторые готовые основные функции, давайте сделаем что-то приятное. Первая полоса на каркасе состоит из трех разделов. Есть верхняя часть с деревом и кнопкой призыва к действию, средняя часть с видео слева и нижний колонтитул с тремя ссылками. Я на самом деле немного изменил это, просто потому, что у меня сейчас нет времени снимать видео, но верхняя часть осталась неизменной на момент написания статьи.

Если вы помните из предыдущих разделов, при загрузке любой страницы используется основной файл index.php. Когда вы смотрите на первую страницу, она ловко распознает ваши намерения и решает включить файл site / index.php. На данном этапе все еще абсолютно ничего не происходит. Этот файл содержит get_header() которая выводит верхнюю часть нашей страницы, за которой следует некоторое содержимое, а затем get_footer() вытягивает нижнюю часть нашей страницы. До тех пор, пока вы не get_header() функцию get_header() выходных данных не будет, поэтому вы можете свободно устанавливать файлы cookie, данные сеанса, выходные HTTP-заголовки и так далее.

Первое, что я понял, это хороший HTML-заголовок для сайта. Ниже приведена верхняя часть файла header-site.php.

1
2
<!—?php get_head(«site») ?—>

Это HTML-тип документа с тегом html, get_head() и тегом body с представлением в атрибуте id (в данном случае это site, но это может быть приложение или ошибка). Я решил использовать функцию для получения всех данных заголовка, таких как заголовок сайта, сценарии, стили и т. Д., Поскольку он чище и предоставляет вам еще один уровень гибкости. Если вы посмотрите на код в functions.php для функции get_head() , все, что она делает, это извлекает файл head-site.php, так что давайте сейчас пойдем туда и посмотрим.

01
02
03
04
05
06
07
08
09
10
11
12
13
Bonsai Writer
<!— Favicons —>
  
  
  
  
  
  
<!— Styles —>
  
<!— Scripts —>
<script type=»text/javascript» src=»http://lesscss.googlecode.com/files/less-1.1.3.min.js»></script>

Там могут быть некоторые дополнительные биты в файлах проекта, но это суть. Мы определили наш заголовок и набор символов, кучу разных значков, привязанных к странице, наш стиль включен вместе с МЕНЬШЕМ JavaScript, извлеченным из Google Code.

Чтобы заставить работать LESS, вам нужно сначала включить таблицу стилей LESS, а затем скрипт LESS. Это автоматически заставит МЕНЬШУ работать как предназначено.

Я не буду объяснять остальную часть HTML здесь; взгляните на файл заголовка и файл LESS, чтобы увидеть, что происходит. В этом нет ничего особенного.


При просмотре страницы с ошибкой, файл 404.php будет включен. Этот файл работает так же, как индексный файл в каталоге сайта. Он включает в себя заголовок, имеет некоторое содержание и включает нижний колонтитул Единственный добавленный бит — это настройка заголовка, которую мы можем сделать в верхней части этого файла, поскольку в этот момент еще нет вывода.

Установка заголовка чрезвычайно важна, так как в противном случае пользователь получит заголовок 200, то есть все в порядке. С кодом, все в порядке, так как мы запрашиваем файл (index.php), который там есть, поэтому у нас есть ресурс, который пользователь ищет на уровне файлов. Однако мы знаем, что у нас нет контента, поэтому мы должны сказать миру, что это на самом деле страница 404.

1
<!—?php header(«HTTP/1.1 404 Not Found»);

Поскольку я предполагаю, что в будущем в приложении будет несколько разных страниц с ошибками, я решил модулировать следующий шаг, а не кодировать его напрямую. Я создал функцию fatal_error() которая принимает два параметра — заголовок и некоторое содержимое — и отображает приятное сообщение об ошибке. Функцию можно найти в functions.php.

01
02
03
04
05
06
07
08
09
10
11
12
function fatal_error($title, $content) {
  get_header(«error»);
  echo «</pre>
<h1>».$title.»</h1>
<pre>
«;
  echo «</pre>
<div class=»error_text»>».$content.»</div>
<pre>
«;
  get_footer(«error»);
}

Сама функция извлекает заголовок ошибки, добавляет заголовок и содержимое, а затем извлекает нижний колонтитул. Файл LESS делает все это красиво и красиво, но в противном случае мы закончили со страницей ошибок.

Это отличный пример плохого планирования и кодирования с моей стороны. Прежде всего, я не считаю ошибку 404 фатальной ошибкой, поэтому функция имеет неправильное имя. Кроме того, поскольку я намереваюсь использовать эту функцию для всех ошибок, было бы гораздо разумнее передать этот аргумент с номером ошибки и заставить его сгенерировать сам заголовок HTTP, который прямо сейчас выполняется вне функции.

Если вы работаете над быстрой версией альфа-версии или создаете усовершенствованный каркас, я думаю, что эти ошибки в порядке. Мы знаем, что они там, мы можем прийти и исправить их позже; мы просто хотим иметь что-то онлайн, чтобы получить обратную связь и попробовать сами.


Мы наконец добрались до точки, где мы можем построить наше приложение. Я заранее знаю, что хочу на этой странице четыре разных вещи. Я хочу сделать хорошую панель заголовка, боковую панель, редактор и область предварительного просмотра. Заголовок всегда будет виден, боковая панель может быть скрыта с помощью комбинации клавиш или щелчка по дереву. По умолчанию виден только редактор, но с помощью комбинации клавиш редактор можно скрыть и показать область предварительного просмотра.

Сначала я зашел в файл header-app.php и убедился, что детали просматриваемой статьи помещаются в переменную. Поскольку мы написали функцию для этого, это не вызовет сильной головной боли, и я смогу обратиться к этому позже.

1
global $article;

Я также позаботился о том, чтобы jQuery был добавлен в файл head-app.php, извлеченный из Google Code. Я добавил файл в каталог js с именем app.js и привязал его к приложению, добавив его в файл head-app.php.

1
2
<script type=»text/javascript» src=»http://ajax.googleapis.com/ajax/libs/jquery/1.6.4/jquery.min.js»></script>
<script type=»text/javascript» src=»<?php echo URL ?>/js/app.js»></script>

Закончив, я зашел в файл app / index.php и создал этот скелет.

1
2
3
4
</pre>
<div id=»app_content»><textarea id=»editor»><?php echo $article->raw ?></textarea>
</div>
<pre>

Я удостоверился, что создал файл menu.php в каталоге приложения и поместил в него текст-заполнитель. Я также скрыл область предварительного просмотра, используя свои навыки CSS. Кроме того, я добавил элементарный стиль, чтобы все было примерно так, как должно быть. Поскольку следующей вещью, о которой я думал, было создание некоторых элементов управления JS, я дал все конфликтующие цвета фона, чтобы я мог видеть, что происходит.

Пришло время написать немного JavaScript! Моим первым заказом было зайти в app.js, написать в него следующее и перезагрузить страницу приложения.

1
2
3
$(document).ready(function() {
  alert(«lol»);
})

При перезагрузке вы должны увидеть окно с текстом «lol». Это подтверждает, что ваш файл JavaScript привязан к приложению и что jQuery также работает. Давайте продолжим, позволив пользователям управлять видимостью меню, используя ctrl+space .

1
2
3
4
5
6
$(document).keydown(function(e) {
  if(e.which == 32 && e.ctrlKey == true) {
    $(«#app_menu»).toggle();
    return false;
  }
})

При использовании функций ключевых событий вы можете использовать параметр функции для получения всевозможной информации о нажатой кнопке. Клавиша пробела имеет код клавиши 32, и атрибут ctrlKey будет true если был нажат элемент управления. Таким образом, если код клавиши действительно 32, а ctrlKey — true , пользователь нажал комбинацию клавиш ctrl + пробел. В этом случае давайте переключим меню — закрываем его, если оно открыто, и наоборот. Мы также return false чтобы убедиться, что пробел не вставлен в текстуру редактора, если мы его используем.

Если вы хотите увидеть все детали, связанные с нажатием клавиши, вы можете использовать следующий метод, который работает в большинстве браузеров, в которых встроен отладчик JavaScript или установлен Firebug .

1
2
3
$(document).keydown(function(e) {
  console.log(e);
})

Сразу после этого я использовал некоторую абстракцию, чтобы отделить переключение меню от события. Две причины этого:

  1. Переключение меню в конечном итоге потребует гораздо больше кода, чем просто эта строка переключения. Нам нужно будет изменить некоторые классы и даже отправить AJAX-вызов, чтобы позже сохранить состояние меню, чтобы оно оставалось закрытым при загрузке страницы, если пользователь закрывает его один раз.
  2. Меню можно контролировать и другими способами, поэтому мы не хотим дублировать код. Вы также можете скрыть и отобразить меню, щелкнув значок дерева, поэтому вставка в код для открытия меню и сохранения его состояния в этом случае также является излишней и ненужной.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
$(document).keydown(function(e) {
  if(e.which == 32 && e.ctrlKey == true) {
    toggle_menu()
    return false
  }
})
function toggle_menu() {
  if($(«#app_menu»).is(«:visible»)) {
    $(«#app_logo»).removeClass(‘current’)
  }
  else {
    $(«#app_logo»).addClass(‘current’)
  }
  $(«#app_menu»).toggle()
}

Давайте будем следовать той же логике при создании действия, которое переключит наш вид из режима редактирования в режим предварительного просмотра, используя комбинацию клавиш Shift + Пробел.

01
02
03
04
05
06
07
08
09
10
$(document).keydown(function(e) {
  if(e.which == 32 && e.shiftKey == true) {
    switch_mode()
    return false
  }
})
function switch_mode() {
  $(«#editor»).toggle();
  $(«#preview»).toggle();
}

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


Если вы знакомы с тем, как WordPress обрабатывает запросы AJAX, то вы на дружественной территории. В противном случае этот метод может показаться немного сложным по сравнению с необходимой нам функциональностью. Я объясню свои причины использования этого метода, как только я покажу вам, как он на самом деле работает.

Все вызовы AJAX в приложении обрабатываются файлом ajax.php в главном каталоге. Этот файл проверяет, что вызов AJAX происходит изнутри приложения, имеет ли он правильные параметры и так далее.

Чтобы получить вызов AJAX для работы в этом приложении, вам нужно сделать как минимум три вещи:

  1. Дайте ему файл ajax.php в качестве целевого URL
  2. Убедитесь, что он передает параметр «действие»
  3. Убедитесь, что в файле функций определена функция с именем переданного параметра действия

Давайте посмотрим на внутренности ajax.php, чтобы лучше понять все это.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
if(isset($_SERVER[«HTTP_REFERER»]) OR empty($_SERVER[«HTTP_REFERER»]) OR substr($_SERVER[«HTTP_REFERER»],0, strlen(URL)) == URL) {
  $title = «A fatal error has occurred»;
  $content = «
  
      It seems like you are trying to initiate an action from outside the app which
      either means that you are a naughty person, or that a very weird error has occured in our app.
      Please <a href=»&quot;.URL.&quot;/contact/»>contact us</a> if you think you have found an error with the app
      
  
    <a class=»button» href=»&quot;.URL.&quot;»>go to the website &raquo;</a>
  «;
  fatal_error($title, $content);
  die();
}
if(isset($_REQUEST[«action»]) OR empty($_REQUEST[«action»]) OR is_string($_REQUEST[«action»])) {
  $title = «A fatal error has occured»;
  $content = «
  
      It seems like there is an incorrect link in our application.
      the action you initiated was not completed.
      if you think you have found an error with the app.
      
  
    <a class=»button» href=»&quot;.$_SERVER[«>&laquo;
  «;
  fatal_error($title, $content);
  die();
}
call_user_func($_REQUEST[«action»]);

В первом блоке if мы проверяем реферер (откуда мы пришли), чтобы удостовериться, что нет никакого шалости. Он должен быть определен, он не может быть пустым и должен начинаться с нашего базового URL. Если это условие не выполняется, мы генерируем страницу ошибки. Второй блок гарантирует, что действие определено. Если что-то не так, мы снова показываем страницу с ошибкой.

Как интересное примечание, именно в этот момент я понял, что должен был определить свой заголовок ошибки в функции fatal_error() . Я также понял, что первая ошибка на самом деле является фатальной ошибкой, а вторая нет; так что было бы лучше переименовать функцию.

Если все хорошо, мы вызываем функцию, которая имеет имя значения нашего параметра действия. Если вы передадите «save_article» в качестве параметра, будет запущена функция save_article() .

В нашем приложении у нас будет только 1-2 AJAX-вызова, поэтому мы можем просто создать для них отдельные файлы. Это верное мнение, но я всегда стараюсь думать заранее. Эта настройка позволяет мне делать гораздо больше. На самом деле, к тому времени, когда вы прочитаете это, я могу немного его расширить, чтобы обеспечить большую гибкость. Возможность «подключить» наши AJAX-вызовы к существующим функциям значительно облегчает жизнь каждого человека и является основой для системы плагинов, которая может появиться, даже если приложение станет популярным.


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

В качестве первого шага, давайте удостоверимся, что мы сохраняем изменения без их преобразования. Давайте добавим немного JavaScript, который будет сохранять нашу статью после каждого 30-го нажатия клавиши.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
k = 0
$(document).keyup( function() {
  if(k % 30 == 0) {
    save_article()
  }
  k++
})
function save_article() {
  raw = $(«#editor»).val()
  uri = $(«body»).attr(«data-uri»)
  $.ajax({
    url: ajaxurl,
    type: «post»,
    data: {raw : raw, uri : uri, action : «action_save_article»},
    success: function(response) {
      $(«#preview»).html(response)
    }
  })
}

Сначала мы определяем переменную k которая содержит количество нажатых клавиш с момента загрузки страницы. Мы добавляем один к этой переменной каждый раз, когда нажимается клавиша. Если переменная может быть разделена на 30 без остатка, мы сохраняем статью.

Функция save_article берет наш простой текст из редактора и URI статьи, который хранится как атрибут тега body. Затем мы делаем AJAX-вызов с помощью jQuery. Мы устанавливаем url в ajaxurl — переменную, определенную в head-app.php — мы устанавливаем тип для публикации и определяем данные, которые мы хотим отправить. Эти данные включают в себя простой текст, URI и действие, которое необходимо для вызова AJAX; это функция, которая будет запущена. Затем мы берем ответ запускаемого сценария PHP и выводим его в окно предварительного просмотра.

Ранее мы создали скелет для функции action_save_article() в файле appFunctions.php; так что давайте пойдем туда сейчас и изменим его.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
function action_save_article() {
  global $bwdb;
  $raw = $_POST[«raw»];
  $uri = $_POST[«uri»];
  $tpos = strpos($raw, «\n»);
  $title = substr($raw, 0, $tpos);
  if (empty($title)) {
    $title = «Untitled Article»;
  }
  $title = $bwdb->real_escape_string($title);
  $body = substr($raw, $tpos);
  $html = convert_plain_to_html($body);
  echo «</pre>
<h1>\n».$title.»\n</h1>
<pre>
\n».stripslashes(stripslashes($html));
  $bwdb->query(«UPDATE article SET title = ‘$title’, raw = ‘$raw’, html = ‘$html’ WHERE uri = ‘$uri’ » );
}
function convert_plain_to_html($plain) {
  return $plain;
}

Для простоты использования я перетащил необработанное текстовое содержимое в $raw а uri — в $uri . Затем я нашел позицию первого разрыва строки. Все до этого — заголовок, все после — содержание. Мы находим заголовок с помощью функции substr() и сохраняем его в переменной $title . В случае, если он пуст, мы должны убедиться, что у нас все еще есть заголовок — «Статья без названия». Наконец, мы должны избежать значения. Это пользовательский ввод, поэтому нам нужно защищаться от вредоносного кода, выполняемого на наших серверах.

Тело текста — это все, кроме заголовка; поэтому мы снова используем substr() чтобы поместить основной текст в переменную $body . На данный момент значение нашей переменной $html установлено равным значению переменной $ body. Я создал отдельную функцию, которая позаботится об этом, но сейчас она просто возвращает простой текст обратно. Мы изменим это через секунду, мы просто хотим убедиться, что все это работает. Если вы введете не менее 30 символов в текстуру и перезагрузите страницу, сохраненный текст должен быть извлечен в текстовую область из базы данных.

Теперь пришло время изменить значение переменной $html чтобы получить фактический HTML. Планируя, как это должно работать, я разработал три основных принципа для себя.

  • Основой преобразования текста в HTML будет символ новой строки. Я разделю весь текст на куски, разделенные новыми строками.
  • Я преобразую эти фрагменты во все, что нужно (элемент списка, блок предварительного кода и т. Д.), Все остальное будет преобразовано в абзац.
  • Эти чанки могут содержать дополнительную разметку для встроенных элементов, таких как ссылки, элементы кода, полужирный текст и т. Д.

Имея это в виду, давайте попробуем. Во-первых, давайте разделим наш текст новыми строками и создадим для каждого фрагмента абзац.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
function convert_plain_to_html($plain) {
  $html = explode(«\n», $text);
  foreach($html as $key => $paragraph) {
    if(trim($paragraph) == «») {
      unset($html[$key]);
    }
    else {
      $html[$key] .= «
  
\n».$paragraph.»\n
  
\n»;
    }
  }
  $html = implode(«\n», $html);
  $html = str_replace(«\n», «
«, $html);
  $html = $bwdb->real_escape_string($html);
  $html = str_replace(«
«, «\n», $html);
  return $html
}

Изначально мы используем explode для разделения нашего текста на массив. Каждая строка будет в отдельном элементе в массиве. Теперь давайте пройдемся по каждому члену массива и добавим несколько тегов абзаца. Если строка пуста, мы удаляем этот элемент, поскольку не хотим, чтобы пустые теги абзаца мешали работе нашего документа. В противном случае мы добавляем тег абзаца до и после текста.

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

Наш массив $html теперь содержит правильный HTML, поэтому мы взорвали его, чтобы сделать его строкой. Чтобы убедиться, что вредоносный код не отправляется на наши серверы, нам нужно экранировать значение переменной, но если мы это сделаем, наши новые строки также будут экранированы. Чтобы этого не случилось, я временно заменил все экземпляры «\ n» на что-то уникальное. Затем мы экранируем строку и снова заменяем наш временный заполнитель символами новой строки. После этого мы можем вернуть наш новый HTML-код.

Каждый элемент уровня блока может содержать несколько встроенных элементов уровня, таких как ссылки или код. Давайте посмотрим, как мы можем обрабатывать ссылки. Прежде всего, нам нужно создать правило, с помощью которого пользователь будет создавать ссылки в текстовой версии. Например, пользователи могут ввести следующее, чтобы создать ссылку

1
<a href=»http://thelink.com»>This is the anchortext</a>

Чтобы разобрать это из текста, мы можем использовать следующий код, который должен быть помещен в convert_plain_to_html() внутри цикла foreach, прежде чем мы преобразуем абзацы.

1
2
3
4
5
6
preg_match_all(«/\<a>([^</a>]*)/», $paragraph, $match_links, PREG_SET_ORDER);
foreach($match_links as $match_link) {
  $link = explode(«|», $match_link[1]);
  $link = «<a href=»&quot;.$link[1].&quot;»>».$link[0].»</a>»;
  $paragraph = str_replace($match_link[0].»]», $link, $paragraph);
}

Прежде чем мы начнем совпадение с регулярным выражением, значение $paragraph представляет собой строку необработанного текста. В этой строке исходного текста мы хотим разобрать все эти ссылки и преобразовать их в реальные ссылки HTML. Мы используем preg_match_all() потому что хотим перехватить все вхождения (в абзаце может быть несколько ссылок). Первый аргумент — это регулярное выражение, второй — предмет, в котором мы ищем. Третий аргумент будет содержать наши данные о совпадениях, а четвертый — флаг, который изменяет способ возврата совпадений. Смотрите документацию для получения дополнительной информации обо всем этом preg_match_all .

Чтобы соответствовать этому шаблону, все, что нам нужно сделать, это найти любое вхождение «[Link:» »и убедиться, что захватывает все, пока мы не доберемся до закрывающей скобки. Мы перебираем все полученные совпадения и анализируем анчортекст и URL-адрес, используя explode. Первым членом массива $ link будет anchortext, а вторым — url. Затем мы создаем ссылку HTML из этих двух элементов. Наконец, мы ищем исходный текст ссылки в текстовой строке и заменяем его нашей новой блестящей ссылкой.

Я также хотел убедиться, что встроенный код может быть размещен следующим образом.

1
You need to use the <code>&lt;code here&gt;</code> tag to create inline code

Еще раз, давайте сделаем некоторые разбор и замену. Вы захотите разместить код ниже, сразу после или до проверки кода предыдущей ссылки.

1
2
3
4
preg_match_all(«/@@([^@]*)@@/», $paragraph, $match_codes, PREG_SET_ORDER);
foreach($match_codes as $match_code) {
  $paragraph = str_replace($match_code[0], «<code>».htmlentities($match_code[1]).»</code>», $paragraph);
}

Мы разбираем те биты, которые начинаются со знака «двойной знак», затем содержат все, кроме знака «А», а затем заканчиваются знаком «двойной» Чтобы обеспечить правильное отображение кода, мы конвертируем все соответствующие символы в объекты HTML и оборачиваем теги кода вокруг объекта. Когда я делаю свой последний обзор этой статьи, я заметил ошибку в приведенном выше коде, но я оставлю ее в качестве хорошего примера того, как получить лучший код с помощью итерации.

Обычно хорошей практикой является поиск строки, начинающейся с определенного символа, и получение всего, кроме некоторого символа. Этот метод используется для разбора тегов HTML и других целей, однако в нашем случае он некорректен. Вышеупомянутое регулярное выражение не будет хорошо работать со встроенным кодом, который содержит символ at хотя нет причин, по которым он не должен содержать его. В нашем случае приведенный выше код должен быть изменен следующим образом:

1
preg_match_all(«/<code>(.*)</code>/», $paragraph, $match_codes, PREG_SET_ORDER);

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

1
2
3
4
5
Let's create a paragraph here, and then a list underneath.
# This is a numbered list
# This is its second member
# And this is its third member
Another paragraph will be written here

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

Если мы дойдем до элемента списка, перед тем, как приступить к анализу, мы должны добавить открывающий тег списка. Поскольку следующий элемент списка — это новая строка, мы будем добавлять открывающие теги перед каждым элементом списка. Поэтому в дополнение к открывающему тегу я также установил глобально доступную переменную — давайте назовем ее $prev_ol— в значение true. То есть мы получаем элемент списка и $prev_olимеем значение false, мы знаем, что только что прибыли в новый список, поэтому мы добавляем начальный тег, добавляем элемент списка и устанавливаем $prev_olзначение true, что указывает на то, что мы теперь в списке. Как только мы перейдем к следующему элементу списка, значение $prev_olвсе равно true, поэтому мы не добавляем открывающий тег, мы просто добавляем элемент списка. Как только мы добираемся до любого элемента, который не является элементом списка, мы проверяем, что если $prev_oltrue, мы добавляем закрывающий список тегов и устанавливаем$prev_olложно. Наш результирующий код выглядит следующим образом.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
function convert_plain_to_html($plain) {
  $html = explode("\n", $text);
  $prev_ol = false;
  foreach($html as $key => $paragraph) {
    if(trim($paragraph) == "") {
      unset($html[$key]);
    }
    else {
      // Parsing inline elements
      preg_match_all("/\<a>([^</a>]*)/", $paragraph, $match_links, PREG_SET_ORDER);
      foreach($match_links as $match_link) {
        $link = explode("|", $match_link[1]);
        $link = "<a href="&quot;.$link[1].&quot;">".$link[0]."</a>";
        $paragraph = str_replace($match_link[0]."]", $link, $paragraph);
      }
      preg_match_all("/@@([^@]*)@@/", $paragraph, $match_codes, PREG_SET_ORDER);
      foreach($match_codes as $match_code) {
        $paragraph = str_replace($match_code[0], "<code>".htmlentities($match_code[1])."</code>", $paragraph);
      }
      // Parsing block level elements
      if(isset($match_ulist[1]) AND !empty($match_ulist[1])) {
        $html[$key] = "";
        if($prev_ol == false) {
$html[$key] .= "</pre>
<ol>
<ol>\n";</ol>
</ol>
&nbsp;
<ol>
<ol>}</ol>
</ol>
&nbsp;
<ol>
<ol>$html[$key] .= "
    <li>\n".$match_olist[1]."\n</li>
</ol>
</ol>
&nbsp;
<ol>
<ol>\n";</ol>
</ol>
&nbsp;
<ol>
<ol>$prev_ol = true;</ol>
</ol>
&nbsp;
<ol>
<ol>}</ol>
</ol>
&nbsp;
<ol>
<ol>else {</ol>
</ol>
&nbsp;
<ol>
<ol>if($prev_ol == true) {</ol>
</ol>
&nbsp;
<ol>$html[$key] .= "</ol>
<pre>
\n";
        }
        $html[$key] .= "
  
\n".$paragraph."\n
  
\n";
        $prev_ol = false;
      }
    }
  }
  $html = implode("\n", $html);
  $html = str_replace("\n", "
", $html);
  $html = $bwdb->real_escape_string($html);
  $html = str_replace("
", "\n", $html);
  return $html
}

Как видите, код здесь быстро усложняется. Нам также нужно было изменить способ обработки тегов абзаца. Поскольку на данный момент что-то является либо списком, либо абзацем, мы должны убедиться, что если мы переключаемся со списка на абзац, то список, который находится до его закрытия. Если мы создаем абзац и $prev_olимеет значение true (мы обрабатывали список до этого абзаца), нам нужно добавить закрывающий тег, прежде чем мы начнем обрабатывать наш абзац. Затем нам нужно убедиться, что $prev_olснова установлено значение false.

— Больше замен —

Чтобы увидеть больше замен, проверьте полную функцию в файлах проекта. Я удовлетворил только несколько возможностей, но я добавляю все больше и больше. Большинство замен можно разделить на один из двух методов, которые я описал выше, поэтому их добавление не должно быть большой проблемой.

— Включение учетных записей пользователей —

Включение пользователей для этого сайта было довольно простым процессом, хотя это в основном из-за ограниченного набора функций. Три основные области работы с Bonsai Writer — это регистрация, аутентификация и обнаружение входа в систему.

Регистрация работает с простой формой, которая позволяет пользователям отправлять свои адреса электронной почты для регистрации. Это делается с помощью вызова AJAX, который запускает action_register()функцию, давайте посмотрим.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
function action_register() {
  global $bwdb;
  $_POST["email"] = trim($_POST["email"]);
  if (!isset($_POST["email"]) OR empty($_POST["email"])) {
    echo 'please enter an email address';
  }
  elseif(!filter_var($_POST["email"], FILTER_VALIDATE_EMAIL)) {
    echo 'please use a valid address';
  }
  elseif(check_user_exists($_POST["email"])) {
    echo 'that email address has already been registered';
  }
  else {
    if($_POST["notify"] != 'true' ) {
      $plain_pass = substr(sha1(time().$_POST["email"]."saltword"),0,8);
      $pass = sha1($plain_pass);
      $subject = "Welcome To Bonsai Writer";
      $message = "</pre>
<h3>Hello and Welcome to Bonsai Writer!</h3>
<pre>
        You can now use the password ".$plain_pass." to log in.
        We strongly advise changing this when you first log in.
  
        We wish you a nice and productive stay, if you have any comments, suggestions or
        problems, feel free to contact us at any time.
  
        Bonsai Writer
      «;
      $headers = 'MIME-Version: 1.0' . "\r\n";
      $headers .= 'Content-type: text/html; charset=iso-8859-1' . "\r\n";
      $headers .= 'From: Bonsai Writer ' . "\r\n";
      mail($_POST["email"], $subject, $message, $headers);
    }
    $email = $bwdb->real_escape_string($_POST["email"]);
    $bwdb->query("INSERT INTO user (email, password) VALUES ('$email', '$pass')");
    if($_POST["notify"] == "true") {
      header("Location: ".$_SERVER["HTTP_REFERER"]."?notify=true");
    }
    else {
      echo "success";
    }
  }
}

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

Пароль генерируется путем создания строки, которая состоит из текущего времени, адреса электронной почты пользователя и дополнительного секретного слова. Это может быть что-то вроде этого: 1317420619me@myemail.comsaltword. Время в начале, в сочетании с адресом электронной почты и случайным словом дает нам довольно случайную базу. Затем мы передаем это через алгоритм SHA1. На данном этапе цель этого не в том, чтобы зашифровать пароль, мы просто хотим что-то случайное, выглядящее как простой пароль. Мы берем первые 8 символов из полученной строки, это будет действительный пароль пользователя, что-то вроде «aXe938Sk». Мы запускаем эту строку по алгоритму SHA1, на этот раз с целью ее шифрования. Мы храним только зашифрованный пароль для безопасности, конечно.

Всякий раз, когда пользователь входит в форму, заполняется, давая нам адрес электронной почты и пароль пользователя. Параметры передаются в action_log_user_in()функцию, которая проверяет правильность деталей. Это делается путем получения электронной почты из формы и запуска заданного пароля через функцию SHA1. Нужно взглянуть на базу данных, чтобы узнать, существует ли пользователь с данным адресом электронной почты / зашифрованным паролем. Если пользователь существует, мы помещаем данные пользователя в переменную сеанса. Если возникла проблема, мы возвращаем ошибку.

В файле load.php я включил раздел, в котором рассматриваются переменные сеанса пользователя. Если $_SESSION["login"]существует — мы устанавливаем это в процессе аутентификации — мы предполагаем, что пользователь вошел в систему, и мы передаем детали в $userdataпеременную. У нас также есть функция, is_logged_in()которая проверяет значение $userdataпеременной и возвращает true, если пользователь вошел в систему, и false, если он / она нет.

Система входа в систему также несколько элементарна и небезопасно безопасна. Я бы предпочел использовать более безопасные $ _COOKIES или некоторые более новые доступные методы, но это было бы хорошим заменителем для тестирования функциональности. Здесь нет указания на какую-либо стандартизацию, чтобы приспособиться к дополнительным методам входа, таким как oAuth или интеграции с такими сервисами, как Twitter и Facebook.


В этом приложении использовался ряд других техник, которые я здесь не освещал, но вы сможете найти их все, просмотрев предоставленные файлы. Например, свойство CSS font-face используется для встраивания трех шрифтов, чтобы сделать веб-сайт красивым, CSS — для создания круглых значков на верхней панели приложения и так далее.

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

Прекрасный набор правил .htaccess находится в файле htaccess, который я получил из Perishable Press . Это поможет уберечь злых хакеров от вашего сайта.

Также можно добавить множество функций. Это только первоначальное воплощение — тестовая версия этого приложения. Он будет поддерживать пользовательское встраивание CSS, определение пользовательских правил и множество удобных функций, помогающих авторам и редакторам выполнять свою работу. Моя цель — получить мгновенный предварительный просмотр статьи в ее родной среде (для любого веб-сайта, для которого я пишу).


Хотя это ни в коем случае не является обширной статьей о том, как создать веб-службу или веб-сайт, я надеюсь, что она дала некоторое представление о рабочем процессе и логике создания чего-то для сети. Помните, вам не нужно все делать правильно в первом раунде. Надо просто уметь делать это все лучше и лучше!

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

В этом уроке мы узнали, как создать элементарный веб-сайт, который сам по себе является навыком. Я вижу много людей, которые знают PHP, HTML, CSS, Javascript; единственное, чего им не хватает, так это способности собрать все это самостоятельно. Имейте в виду, что это только один из способов ведения дел. Во многих случаях лучше использовать популярные фреймворки и готовые шаблоны, но всегда приятно знать, как это сделать самостоятельно.