Содержание


Создание и развертывание масштабируемого приложения для управления контактами в облаке. Часть 1

Создание приложения с помощью Bluemix, PHP, MongoDB и jQuery Mobile

Comments

СУБД MongoDB стала популярным выбором для облачных приложений, управляемых базой данных, благодаря своей гибкой, документально-ориентированной схеме и простой масштабируемости. Я впервые услышал о MongoDB в начале 2012 года, когда движение NoSQL только набирало обороты, а компания MongoDB еще называлась 10gen. Сегодня MongoDB – одна из самых популярных СУБД на планете, используемая в самых разнообразных приложениях от аналитики реального времени до визуализации данных, собираемых от датчиков «Интернета вещей» (IoT). Но лучшие качества MongoDB остаются все теми же: простата настройки и эксплуатации, гибкая модель данных, распределенная архитектура. 

В первой части этого руководства из двух частей я покажу, как использовать эти особенности MongoDB для создания мощных, масштабируемых облачных приложений. Чтобы продемонстрировать, как это легко, я познакомлю вас с процессом создания размещенного в облаке IBM® Bluemix™ PHP/jQuery-приложения «Адресная книга», которое позволяет хранить контактные адреса и управлять ими с помощью базы данных MongoDB. Естественно, при этом поддерживаются мобильные функции, так что, развернув приложение в IBM Bluemix, можно сразу же начать работать с ним через планшет или смартфон (см. Часть 2).

Заинтересовались? Тогда читайте дальше.

Что нужно для создания приложения

Пример приложения позволяет вводить контактные данные, включая имя, адрес электронной почты и номера телефонов. Приложение работает в облаке Bluemix и сразу же может использоваться на сенсорных устройствах, таких как планшеты и смартфоны. Оно также поддерживает несколько пользователей, позволяя им входить в приложение с помощью своих реквизитов Google+. Проверка подлинности выполняется с помощью протокола OAuth 2.0 и API Google+.

Со стороны клиента я использую jQuery Mobile для создания пользовательского интерфейса приложения, оптимизированного для мобильных устройств. На сервере я использую Slim, PHP-микросреду, которая позволяет управлять логикой приложения и сохранять/извлекать данные из MongoDB, а также HybridAuth, библиотеку с открытым исходным кодом для аутентификации пользователей.

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

Примечание. Любое приложение, использующее API-интерфейс Google+, должно соблюдать условия обслуживания и политику конфиденциальности Google. Перед началом проекта потратьте несколько минут на чтение этих правил и следите за тем, чтобы в вашем приложении они соблюдались.

Запустить приложениеПолучить код

Шаг 1. Создание заготовки приложения.

На первом шаге мы создадим заготовку приложения с PHP-микросредой Slim. Ее можно легко загрузить и установить с помощью менеджера PHP-зависимостей Composer.

  1. Перейдите в корневой каталог документов веб-сервера (обычно /usr/local/apache/htdocs на Linux или C:\Program Files\Apache\htdocs на Windows) и создайте новый каталог для приложения.
    shell> cd /usr/local/apache/htdocs 
    shell> mkdir contacts

    В этой статье он будет называться $APP_ROOT.

  2. Создайте файл конфигурации Composer, который нужно сохранить в каталоге $APP_ROOT/composer.json:
    { 
        "require": { 
            "slim/slim": "2.*" 
        } 
    }
  3. Теперь можно установить Slim с помощью команды Composer:
    shell> cd /usr/local/apache/htdocs/contacts 
    shell> php composer.phar install

Примечание. В данной конфигурации ваше приложение напрямую доступно по URL-адресу http://localhost/contacts. Не нужно пытаться разработать приложение с использованием виртуального хоста (например, http://contacts.localhost), так как Google не позволяет использовать для OAuth-аутентификации никакие частные URL-домены, кроме http://localhost. Конечно, это относится только к локальной разработке; при развертывании в Bluemix ваш URL-домен Bluemix будет общедоступным и, следовательно, может использоваться для переадресации в OAuth без всяких ограничений.

Шаг 2. Добавление пользовательского интерфейса

Следующий шаг заключается в создании простого пользовательского интерфейса, который выдает список доступных контактов и содержит элементы управления для добавления новых контактов и редактирования или удаления существующих. В листинге 1 содержится базовая структура этого пользовательского интерфейса, которую нужно сохранить в файле $APP_ROOT/templates/main.tpl.php.

<!DOCTYPE html>  
<html>  
<head>  
  <meta name="viewport" content="width=device-width, initial-scale=1">  
  <link rel="stylesheet" href="http://code.jquery.com/mobile/1.4.2/jquery.mobile-1.4.2.min.css" /> 
  <script src="//code.jquery.com/jquery-1.9.1.min.js"></script> 
  <script src="//code.jquery.com/mobile/1.4.2/jquery.mobile-1.4.2.min.js"></script> 
</head>  
<body> 
  <div data-role="page"> 
    <div data-role="header"> 
      <h1>Contacts</h1> 
    </div> 
    <div data-role="content"> 
      <a href="<?php echo $baseUri; ?>/save" data-ajax="false" data-inline="true" data-role="button" data-icon="plus" data-mini="true"data-theme="a">Add</a> 
    </div>     
  </div> 
</body> 
</html>

Страница отформатирована согласно стандарту jQuery Mobile. Первичным элементом страницы служит элемент <div> с атрибутом data-role="page". В нем имеются отдельные элементы <div> для заголовка и тела страницы. Тело страницы пока состоит из одной кнопки для добавления новых контактов; однако по мере разработки приложения добавятся список контактов и строка поиска.

На сервере Slim позаботится о визуализации этого шаблона в ответ на запросы по таким URL-маршрутам, как / или /index. Вот так:

<?php 
// Использование автозагрузчика Composer 
require 'vendor/autoload.php'; 
 
// Настройка экземпляра Slim-приложения  
// Инициализация приложения 
$app = new \Slim\Slim(array( 
  'debug' => true, 
  'templates.path' => './templates' 
)); 
 
// Обработчики страницы index  
$app->get('/', function () use ($app) { 
  $app->redirect($app->urlFor('index')); 
}); 
 
$app->get('/index', function () use ($app) { 
  $app->render('main.tpl.php'); 
})->name('index'); 
 
// Перехватчик для добавления пути URI запроса в качестве переменной шаблона 
$app->hook('slim.before.dispatch', function() use ($app) { 
  $app->view()->appendData(array( 
    'baseUri' => $app->request()->getRootUri() 
  )); 
});  
 
$app->run();

Приведенный выше сценарий, который нужно сохранить как файл $APP_ROOT/index.php, представляет собой главный управляющий сценарий приложения. Он начинается с загрузки среды Slim и инициализации нового объекта Slim-приложения. Объект приложения передается в конфигурационный массив, содержащий местоположение каталога с шаблонами страниц, включая тот, что мы создали ранее.

После настройки объекта Slim-приложения сценарий настраивает обратный вызов маршрутизатора для маршрута /index и определяет функцию, вызываемую в случае соответствия этого маршрута входящему запросу. В приведенном выше сценарии обратный вызов /index просто отображает шаблон главной страницы приложения, содержащий различные элементы страницы jQuery Mobile. Сценарий также настраивает обратный вызов маршрутизатора для маршрута /, который просто выполняет перенаправление по маршруту /index.

Обратите также внимание на перехватчик slim.before.dispatch, который извлекает URL-адрес текущего запроса (включая пути подкаталогов, если таковые имеются) и делает его доступным в качестве переменной шаблона с именем $baseUri. Это максимизирует переносимость, поскольку позволяет перемещать приложение в другой каталог на веб-сервере без необходимости переписывать URL-пути представлений. Это можно увидеть в действии в предыдущем шаблоне, в гиперссылке на URL-адрес кнопки Add.

Slim зависит от перезаписи дружественных URL-адресов, так что это также подходящий момент для настройки правил перезаписи URL-адресов на веб-сервере. Если вы используете Apache, придерживайтесь следующих правил перезаписи, которые нужно сохранить в каталоге $APP_ROOT/.htaccess:

<IfModule mod_rewrite.c> 
  RewriteEngine On 
  RewriteCond %{REQUEST_FILENAME} !-d 
  RewriteCond %{REQUEST_FILENAME} !-f 
  RewriteRule ^ index.php [L,QSA] 
</IfModule>

Если вы используете nginx, то за информацией о том, как настроить перезапись URL-адресов, обращайтесь к документации Slim Framework.

Теперь, когда у вас есть доступ к приложению на http://localhost/contacts или http://localhost/contacts/index, вы должны увидеть шаблон страницы jQuery Mobile, созданный ранее:

Шаг 3. Обработка новых контактов

В шаблоне страницы, который мы создали, уже есть кнопка Add с гиперссылкой на URL-маршрут /save. Следующий логический шаг — конкретизировать эту функциональность путем добавления формы и процессора форм для новых контактов. Для этого создайте следующую простую веб-форму, которую нужно сохранить как $APP_ROOT/templates/form.tpl.php:

<!DOCTYPE html>  
<html>  
<head>  
  <meta name="viewport" content="width=device-width, initial-scale=1">  
  <link rel="stylesheet" href="http://code.jquery.com/mobile/1.4.2/jquery.mobile-1.4.2.min.css" /> 
  <script src="//code.jquery.com/jquery-1.9.1.min.js"></script> 
  <script src="//code.jquery.com/mobile/1.4.2/jquery.mobile-1.4.2.min.js"></script> 
</head>  
<body>  
    <div data-role="page"> 
      <div data-role="header"> 
        <h1>Add Contact</h1> 
      </div> 
      <div data-role="content"> 
        <div data-role="collapsible-set" data-inset="false"> 
          <form method="post" data-ajax="false" action="<?php echo $baseUri; ?>/save"> 
            <label for="name">Name</label> 
            <input name="name" id="name" data-clear-btn="true" type="text" /> 
            <label for="email">Email address</label> 
            <input name="email" id="email" data-clear-btn="true" type="text" /> 
            <label for="phone">Phone</label> 
            <input name="phone" id="phone" data-clear-btn="true" type="text" /> 
            <input name="submit" value="Save" type="submit" data-icon="check" data-inline="true" data-mini="true" data-theme="a" /> 
            <a href="<?php echo $baseUri; ?>/index" data-role="button" data-inline="true" data-icon="back" data-mini="true" data-theme="a">Back</a> 
          </form> 
      </div> 
    </div> 
</body> 
</html>

Ничего особенного здесь нет — это просто стандартная форма с полями имени, адреса электронной почты и номера телефона:

При передаче данные формы передаются посредством запроса POST в конечную точку URL /save. В этой точке Slim должен обработать данные формы, дезинфицировать и проверить их, а затем сохранить в базе данных. Для этого нужно внести следующие изменения в файл $APP_ROOT/index.php, чтобы настроить необходимые обработчики маршрута:

<?php 
// Использование автозагрузчика Composer 
require 'vendor/autoload.php'; 
 
// Настройка реквизитов доступа 
// ... к MongoDB 
$config["db"]["uri"] = 'mongodb://db2:db2@192.168.56.101:27017/db2'; 
$config["db"]["name"] = substr(parse_url($config["db"]["uri"], PHP_URL_PATH), 1); 
 
// Настройка экземпляра приложения Slim 
// Инициализация приложения 
$app = new \Slim\Slim(array( 
  'debug' => true, 
  'templates.path' => './templates' 
)); 
 
// Инициализация объекта клиента Mongo 
$mongo = new MongoClient($config["db"]["uri"], array("connectTimeoutMS" => 30000)); 
$db = $mongo->selectDb($config["db"]["name"]); 
 
// Обработчики страницы index – сокращено! 
 
// Добавление обработчиков 
$app->get('/save', function () use ($app) { 
  $app->render('form.tpl.php'); 
}); 
 
$app->post('/save', function () use ($app, $db) { 
  $collection = $db->contacts;   
  $name = trim(strip_tags($app->request->params('name'))); 
  $email = trim(strip_tags($app->request->params('email'))); 
  $phone = trim(strip_tags($app->request->params('phone'))); 
  $contact = new stdClass; 
  if (!empty($name)) { 
    $contact->name = $name; 
    $contact->phone = $phone; 
    $contact->email = $email; 
    $collection->save($contact); 
  } 
  $app->redirect($app->urlFor('index')); 
}); 
 
// hook to add request URI path as template variable – snipped! 
 
$app->run();

Здесь довольно много дополнений, так что давайте рассмотрим их подробнее.

  1. Сценарий начинается с настройки URI для вашего локального экземпляра MongoDB, включая имя пользователя, пароль и используемую базу данных. Затем эти данные о конфигурации используются для инициализации нового объекта PHP MongoClient, который служит главным пунктом управления всеми будущими взаимодействиями с сервером MongoDB.
  2. Далее, сценарий устанавливает обработчик маршрута GET для конечной точки /save. Это просто воспроизведение шаблона формы, показанного выше.
  3. Наконец, имеется обработчик маршрута POST для той же конечной точки, который отвечает за обработку данных формы, и это несколько интереснее. Он начинается с использования объекта MongoClient для инициализации нового объекта MongoCollection с именем contacts; этот объект MongoCollection служит хранилищем всех контактов, добавленных с помощью приложения. Затем обработчик извлекает входные данные, введенные в форму, и дезинфицирует их с помощью функции PHP strip_tags() (это также хорошее место для проверки входных данных — здесь этот шаг пропущен для краткости). Извлеченные таким образом имя, адрес электронной почты и номер телефона прикрепляются как свойства объекта PHP, а затем этот объект сохраняется в базе данных MongoDB с помощью метода save() объекта MongoCollection. После сохранения клиент перенаправляется обратно на страницу /index.

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

Не беспокойтесь – очень скоро мы добавим эту информацию в приложение!

Шаг 4. Извлечение и отображение существующих контактов

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

<?php 
// Настройка и инициализация – сокращено! 
 
// Обработчики страницы index  
$app->get('/', function () use ($app) { 
  $app->redirect($app->urlFor('index')); 
}); 
 
$app->get('/index', function () use ($app, $db) { 
  $collection = $db->contacts; 
  $contacts = $collection->find(); 
  $app->render('main.tpl.php', array('contacts' => $contacts)); 
})->name('index'); 
 
// Другие обработчики и перехватчики – сокращено! 
 
$app->run();

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

Вот отредактированный код $APP_ROOT/templates/main.tpl.php:

<!DOCTYPE html>  
<html>  
<head>  
  <meta name="viewport" content="width=device-width, initial-scale=1">  
  <link rel="stylesheet" href="http://code.jquery.com/mobile/1.4.2/jquery.mobile-1.4.2.min.css" /> 
  <script src="//code.jquery.com/jquery-1.9.1.min.js"></script> 
  <script src="//code.jquery.com/mobile/1.4.2/jquery.mobile-1.4.2.min.js"></script>  
</head>  
<body> 
  <div data-role="page"> 
    <div data-role="header"> 
      <h1>Contacts</h1> 
    </div> 
    <div data-role="content"> 
      <ul data-role="listview" data-inset="true" data-filter="true" data-filter-placeholder="Search contacts..."> 
        <?php foreach ($this->data['contacts'] as $c): ?> 
        <li> 
            <h3><?php echo $c['name']; ?></h3>  
            <div data-type="horizontal"> 
              <a data-inline="true" data-ajax="false" data-role="button" data-icon="mail" data-theme="b" data-mini="true" href="mailto:<?php echo $c['email']; ?>"><?php echo $c['email']; ?></a> 
              <a data-inline="true" data-ajax="false" data-role="button" data-icon="phone" data-theme="b" data-mini="true" href="tel:<?php echo $c['phone']; ?>"><?php echo $c['phone']; ?></a> 
            </div> 
            <div data-type="horizontal"> 
              <a href="<?php echo $baseUri; ?>/save/<?php echo $c['_id']; ?>" data-ajax="false" data-inline="true" data-role="button" data-icon="edit" data-mini="true" data-theme="a">Update</a> 
              <a href="<?php echo $baseUri; ?>/delete/<?php echo $c['_id']; ?>" data-ajax="false" data-inline="true" data-role="button" data-icon="delete" data-mini="true" data-theme="a">Remove</a> 
            </div> 
        </li> 
        <?php endforeach; ?> 
      </ul>  
      <a href="save" data-ajax="false" data-inline="true" data-role="button" data-icon="plus" data-mini="true"data-theme="a">Add</a> 
    </div>     
  </div> 
</body> 
</html>

Здесь цикл foreach() выполняет обход массива контактной информации, извлеченного из коллекции MongoDB, и выводит на экран имя каждого контакта, адрес электронной почты и номер телефона. Кроме того, к каждой записи прикреплены две кнопки для редактирования и удаления контакта, с гиперссылками на URL-адреса конечных точек /save и /delete соответственно. Каждая гиперссылка дополнительно содержит уникальный идентификатор записи контакта (автоматически присвоенный MongoDB) в качестве параметра маршрута – например, /save/550927398ab33a6c1400002a и /delete/550927398ab33a6c1400002a. В следующем разделе мы рассмотрим, как использовать эту информацию.

Одно интересное место в предыдущем примере — атрибут data-filter="true" в представлении списка. Он автоматически добавляет в верхнюю часть списка виджет строку поиска jQuery Mobile, которая позволяет быстро фильтровать список контактов. Вот пример этого в действии:

Шаг 5. Обработка изменения и удаления контакта

Сначала рассмотрим, как удалить контакт. Как описано выше, рядом с каждой записью контакта есть кнопка Remove с гиперссылкой на конечную точку URL /delete и уникальным идентификатором записи. Добавим обработчик для этого маршрута, который считывает ID записи и вызывает метод объекта remove() MongoCollection для удаления соответствующей записи из MongoDB. Вот код, который нужно добавить в файл $APP_ROOT/index.php:

<?php 
// Настройка и инициализация – сокращено! 
 
// Обработчик удаления 
$app->get('/delete/:id', function ($id) use ($app, $db) { 
  $collection = $db->contacts; 
  $collection->remove(array('_id' => new MongoId($id))); 
  $app->redirect($app->urlFor('index')); 
}); 
 
// Другие обработчики и перехватчики – сокращено! 
 
$app->run();

Редактирование существующего контакта чуть сложнее. Во-первых, нужно изменить обработчик GET для конечной точки URL /save, чтобы он считывал ID записи, переданный в качестве параметра маршрута, а затем извлекал соответствующую запись из MongoDB:

<?php 
// Настройка и инициализация – сокращено! 
 
// Обработчики добавления и редактирования 
$app->get('/save(/:id)', function ($id = null) use ($app, $db) { 
  $collection = $db->contacts; 
  $contact = $collection->findOne(array('_id' => new MongoId($id))); 
  $app->render('form.tpl.php', array('contact' => $contact)); 
}); 
 
// Другие обработчики и перехватчики – сокращено! 
 
$app->run();

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

<!DOCTYPE html>  
<html>  
<head>  
  <meta name="viewport" content="width=device-width, initial-scale=1">  
  <link rel="stylesheet" href="http://code.jquery.com/mobile/1.4.2/jquery.mobile-1.4.2.min.css" /> 
  <script src="//code.jquery.com/jquery-1.9.1.min.js"></script> 
  <script src="//code.jquery.com/mobile/1.4.2/jquery.mobile-1.4.2.min.js"></script> 
</head>  
<body>  
    <div data-role="page"> 
      <div data-role="header"> 
        <h1>Add Contact</h1> 
      </div> 
      <div data-role="content"> 
        <div data-role="collapsible-set" data-inset="false"> 
          <form method="post" data-ajax="false" action="<?php echo $baseUri; ?>/save"> 
            <input name="id" type="hidden" value="<?php echo $this->data['contact']['_id']; ?>" /> 
            <label for="name">Name</label> 
            <input name="name" id="name" data-clear-btn="true" type="text" value="<?php echo $this->data['contact']['name']; ?>" /> 
            <label for="email">Email address</label> 
            <input name="email" id="email" data-clear-btn="true" type="text" value="<?php echo $this->data['contact']['email']; ?>" /> 
            <label for="phone">Phone</label> 
            <input name="phone" id="phone" data-clear-btn="true" type="text" value="<?php echo $this->data['contact']['phone']; ?>" /> 
            <input name="submit" value="Save" type="submit" data-icon="check" data-inline="true" data-mini="true" data-theme="a" /> 
            <a href="<?php echo $baseUri; ?>/index" data-role="button" data-inline="true" data-icon="back" data-mini="true" data-theme="a">Back</a> 
          </form> 
      </div> 
    </div> 
</body> 
</html>

Обратите внимание, что форма теперь содержит дополнительное скрытое поле для записи идентификатора. Наконец, нужно отредактировать обработчик POST для конечной точки URL /save, чтобы он находил скрытый ID записи и использовал его для обновления записи контакта в базе данных. Вот отредактированный текст обработчика:

<?php 
// Настройка и инициализация – сокращено! 
 
$app->post('/save', function () use ($app, $db) { 
  $collection = $db->contacts;   
  $name = trim(strip_tags($app->request->params('name'))); 
  $id = trim(strip_tags($app->request->params('id'))); 
  $email = trim(strip_tags($app->request->params('email'))); 
  $phone = trim(strip_tags($app->request->params('phone'))); 
  $contact = new stdClass; 
  if (!empty($name)) { 
    $contact->name = $name; 
    $contact->phone = $phone; 
    $contact->email = $email; 
    if (!empty($id)) { 
      $contact->_id = new MongoId($id); 
    } 
    $collection->save($contact); 
  } 
  $app->redirect($app->urlFor('index')); 
}); 
// Другие обработчики и перехватчики – сокращено! 
 
$app->run();

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

В Части 2 этого руководства я покажу, как добавить поддержку нескольких пользователей и развернуть приложение в Bluemix.


Ресурсы для скачивания


Похожие темы


Комментарии

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

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=Облачные вычисления, Мобильные приложения
ArticleID=1030080
ArticleTitle=Создание и развертывание масштабируемого приложения для управления контактами в облаке. Часть 1
publish-date=04152016