当时方位: 主页 > Linux学院 > 程序设计 > PHP > 运用PHP、MySQL和AngularJS构建移动友爱卡路里计数器

运用PHP、MySQL和AngularJS构建移动友爱卡路里计数器

2014-07-24 15:38 来历:IBM 作者:Vikram Vaswani 人气指数: 我要谈论
它运用 Slim PHP 微型结构与 Nutritionix API 通讯来履行信息检索,运用 jQuery Mobile 来树立用户界面,还运用了 PHP CloudFoundry 构建块和 SendGrid 服务实例。IBM® Bluemix™ 是一款 beta 级产品,跟着咱们不断让其功用愈加完善和更易于运用,它也将不断改进。咱们会尽心竭力坚持本文最新,但并不总是彻底跟得上现状。感谢咱们的了解!

直到几年前,要核算您刚吃的三明治中含有多少卡路里,还需求依托猜想和查看包装。现在,相同的信息可从许多在线养分数据库中获取,这使得盯梢食物摄入量变得更简略。

本文将介绍怎么创立一个在线卡路里计数器,它使得用户能够:

  • 按称号查找食物,经过一个 API 从在线养分数据库 Nutritionix 中检索成果。
  • 运用一个 PHP/AngularJS 运用程序,将选定的食物分组在一同来创立用餐记载,并将这些记载与它们的卡路里计数一同保存在一个 MySQL 数据库中。
  • 检索他们今日、最近七天和最近 30 天耗费的卡路里总量。
  • 从平板电脑和智能电话等移动设备拜访该运用程序。

在客户端,我运用 jQuery Mobile 为该运用程序创立了一个移动友爱的用户界面,并运用 AngularJS 来启用该运用程序的一些交互式特性。在上,我运用 Slim(一个 PHP 微型结构)来操控与 Nutritionix API 的交互,并在 MySQL 中保存和检索数据。

最终一部分将会介绍怎么将运用程序布置到 Bluemix 云,该云供给了一种用于运用程序布置的、可扩展、健全的根底架构,可保证用户具有全天候的拜访才能。

听起来是否很风趣?让咱们开端吧!

完结该运用程序的先决条件

  • 根本了解 jQuery Mobile、AngularJS、PHP、MySQL,以及 Apache 或 nginx
  • 一个具有外寄邮件(outgoing mail server)的本地 PHP/MySQL 开发环境
  • 一个 Bluemix 帐户
  • 一个 Nutritionix API 帐户
  • Composer(PHP 依靠项办理器)
  • CloudFoundry 指令行东西
  • 一个文本编辑器或 IDE

第 1 步. 设置运用程序数据库

运用下面这个代码清单(包括一个 MySQL 表界说和示例数据)来设置运用程序数据库。

  • 假如仅在本地进行开发和布置,那么能够运用此代码初始化一个 MySQL 数据库表,让运用程序衔接到该表。
  • 假如在 Bluemix 进步行布置,那么能够暂时越过此进程;在初始化 Bluemix 上的一个 MySQL 服务实例并绑定它之后,我会在 第 8 步 介绍怎么布置它。
CREATE TABLE meals (
  id int(11) NOT NULL AUTO_INCREMENT,
  uid varchar(255) NOT NULL,
  calories decimal(10,2) NOT NULL,
  rdate timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  ip varchar(20) NOT NULL,
  PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8


CREATE TABLE users (
  id int(11) NOT NULL AUTO_INCREMENT,
  email varchar(255) NOT NULL,
  `password` varchar(255) NOT NULL,
  code varchar(255) DEFAULT NULL,
  `status` int(11) NOT NULL,
  rdate timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  ip varchar(20) NOT NULL,
  PRIMARY KEY (id)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8;

第 2 步. 装置 Slim

下载并设置 Slim 微型结构。为什么挑选 Slim?Slim 包括一个杂乱的 URL 路由器,并且支撑 Flash 音讯、加密 cookie 和中心件。它也简略了解和运用,并且具有优异的文档。

我运用 Composer(PHP 依靠项办理器)下载和设置 Slim。除了 Slim 之外,我还增加了针对 PHP 的 SendGrid 客户端库。(进一步了解 SendGrid 和为什么需求它。)下面这个代码清单是 Composer 配备文件。将此文件保存到 $APP_ROOT/composer.json(其间 $APP_ROOT 指您的作业目录)。

{
    "require": {
        "slim/slim": "2.*",
        "sendgrid/sendgrid": "2.0.5"
    }
}

现在能够运用 Composer 和以下指令装置 Slim:

shell> php composer.phar install

要使该运用程序更简略拜访,还能够在开发环境中界说一个新虚拟主机,并将它的文档根指向 $APP_ROOT。引荐履行这一步(可是可选的),由于这会为 Bluemix 上的方针布置环境创立一个很挨近的副本。

要在 Apache 中为该运用程序设置一个命名虚拟主机,能够翻开 Apache 配备文件(httpd.conf 或 httpd-vhosts.conf)并增加以下代码:

NameVirtualHost 127.0.0.1
<VirtualHost 127.0.0.1>
    DocumentRoot "/var/www/calories"
    ServerName calories.localhost
</VirtualHost>

要在 nginx 中为该运用程序设置一个命名虚拟主机,能够翻开 nginx 配备文件 (nginx.conf) 并增加以下代码:

server {
    server_name calories.localhost;
     root /var/www/calories;
     try_files $uri /index.php;
     
     location ~ \.php$ {
        try_files $uri =404;            
        include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
 # assumes you are using php-fcgi
        fastcgi_pass 127.0.0.1:90;
    }        
}

这些代码界说了一个新的虚拟主机 http://calories.localhost/,该主机的文档根对应于 $APP_ROOT(请记住更新它来反映您自己的本地设置)。重新启动 Web ,以便激活这些新设置。您或许需求更新网络的本地 DNS 来指示运用这个新主机。

第 3 步. 了解 Nutritionix API

与其他许多 Web API 相同,Nutritionix API 经过 HTTP 进行作业,要求将 HTTP 恳求发送到指定的端点。收到这个恳求后,API 会运用包括一个恳求的数据的 JSON 源来回复查询。然后,它能够运用一种端编程言语(比方 PHP 或 Perl)或一个客户端东西包(比方 jQuery 或 AngularJS)来解析此数据,并从中提取内容,以便将它们集成到一个网页中。

注册一个 Nutritionix API 帐户并取得有用的 appId 和 appKey 后,能够运用该 API 作为测验渠道,查找与词汇 “chicken” 匹配的食物。这个免费的开发人员帐户仅支撑每天查找 500 次(但您能够向 API 团队发送电子邮件来恳求进步这一约束)。

看看下一幅图,其间显现了对一个针对 https://api.nutritionix.com/v1_1/search/chicken?fields=item_name,brand_name,nf_calories&item_type=3&appId=[APP-ID]&appKey=[APP-KEY](用于查找查询的 API 端点)的经过验证的 GET 恳求的呼应(在宣布恳求之前,请记住更新曾经的 URL,以反映您的 API 凭证)。

点击这儿给我发音讯

如该图所示,Nutritionix API 运用一个 JSON 文档呼应该恳求,该文档列出了与查找词汇 “chicken” 匹配的食物。查询字符串包括 item_type=3参数,该参数将查找规模约束到 USDA 数据库。关于每种食物,呼应包括食物称号、品牌称号和卡路里计数。也支撑其他字段;请查阅Nutritionix API 文档 了解有关的具体信息。

第 4 步. 启用查找界面

开发一个简略的查找界面,让用户能够查找食物并查看成果列表。成果页有必要包括一些控件,用户能够运用这些控件将所选的食物增加到其用餐记载中。

奢华此用户界面的根本结构并将其保存为 $APP_ROOT/templates/main.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="http://code.jquery.com/jquery-1.9.1.min.js"></script>
  <script src="http://code.jquery.com/mobile/1.4.2/jquery.mobile-1.4.2.min.js"></script>
  <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.0-beta.6/angular.min.js"></script>
</head>
<body>

  <div data-role="page">

    <div data-role="header">
      <h1>Calorie Counter</h1>
    </div>

    <div data-role="content" ng-app="myApp">	
      <div data-role="tabs" ng-controller="myAppController">
      
        <div data-role="navbar">
          <ul>
            <li><a href="#search" data-theme="a" class="ui-btn-active">Search</a></li>
            <li><a href="#record" data-theme="a">Record</a></li>
            <li><a href="#report" data-theme="a">Report</a></li>
          </ul>
        </div>
        
        <div id="search"></div>

        <div id="record"></div>
        
        <div id="report"></div>

      </div>      
    </div>
    
  </div>

</body>
</html>

前面的代码清单显现了一个契合规范 jQuery Mobile 约好的格局的页面。首要页面元素是一个 <div> 元素,它有一个 data-role="page" 特点。这个 <div> 元素包括针对页眉和内容的不同 <div> 元素。页面内容包括一系列选项卡。每个选项卡表明一个使命(“search”、“record” 和 “report”)。单击顶部导航栏中的选项卡称号将会显现它的内容。

接下来,向 search 选项卡增加元素,如下面这个代码清单所示:

<!DOCTYPE html>
...
    <div data-role="content" ng-app="myApp">	
      <div data-role="tabs" ng-controller="myAppController">
       
        <div id="search">
          <h2 class="ui-bar ui-bar-a">Food Item Search</h2>
          <div class="ui-body">
              <input type="search" name="query" ng-model="foodItems.query" />
              <button ng-click="search()">Search</button>
          </div>   
          
          <h2 class="ui-bar ui-bar-a">Search Results</h2>   
          <div class="ui-body">
            <ul data-role="listview" data-split-theme="d">
              <li ng-repeat="r in foodItems.results">
                <a>{{r.fields.item_name}} / {{r.fields.nf_calories + ' calories'}}</a>
<a href="#" data-inline="true" data-role="button" data-icon="plus" 
                  data-theme="a" ng-click="addToMeal(r)">Add</a>
              </li>
            </ul>                    
          </div>
        </div>

      </div>      
    </div>
...

search 选项卡现在包括两个区域:顶部的查找输入字段和底部的查找成果列表。两个区域由一个 AngularJS 操控器操控,二者都运用了一个名为 foodItems 的 AngularJS 模型。下面这个代码清单显现了该操控器的代码。

  <script>
  var myApp = angular.module('myApp', []);
 
  function myAppController($scope, $http) {
    // related to search functionality
    $scope.mealItems = [];
    $scope.foodItems = {};
    $scope.foodItems.results = [];
    $scope.foodItems.query = '';
    
    $scope.search = function() {
      if ($scope.foodItems.query != '') {
        $http({
            method: 'GET',
            url: '/search/' + $scope.foodItems.query,
          }).
          success(function(data) {
            $scope.foodItems.results = data.hits;
          });
      };
    };
    
    $scope.addToMeal = function(foodItem) {
       $scope.mealItems.push(foodItem);
    };     
  }
  </script>

这是该查找界面的实践外观。

运用PHP、MySQL和AngularJS构建移动友爱卡路里计数器

它的作业原理是什么?用户输入一个查找词汇并单击 Search 时,AngularJS search() 函数会经过 foodItems 模型检索输入,并向 /search 运用程序端点生成一个 Ajax 恳求。此恳求不是 Nutritionix API 端点,而是一个由运用程序自身办理的中心 API 端点(稍后将会具体介绍)。

Ajax 恳求的呼应是一个 JSON 包,与之前显现的内容相似。这段响被附加到 foodItems.results 特点中,AngularJS 数据绑定担任迭代此调集,解析它,并将它显现为一个查找成果列表。

请细心看看这个查找界面。请注意,每个查找成果周围都有一个按钮,该按钮链接到 addToMeal() 函数。单击此按钮时,相应的食物会增加到该规模内的一个 mealItems 数组中。稍后,体系会运用这个数组构建 record 选项卡的视图。

在端,需求一个处理函数来处理对 /search 端点的 Ajax 恳求,这时就需求运用 Slim。Slim 运用 Ajax 恳求数据衔接到 Nutritionix API 并运转一次查找,相似于之前显现的查找。下面这个代码清单给出了完结此功用的代码。

<?php
// use Composer autoloader
require 'vendor/autoload.php';
\Slim\Slim::registerAutoloader();

// configure Slim application instance
$app = new \Slim\Slim();
$app->config(array(
  'debug' => true,
  'templates.path' => './templates'
));

// configure credentials
// ... for Nutritionix
$config["nutritionix"]["appId"] = 'APP-ID';
$config["nutritionix"]["appKey"] = 'APP-KEY';

// index page handlers
$app->get('/', function () use ($app) {
  $app->redirect('/index');
});

$app->get('/index', function () use ($app) {
  $app->render('main.php');
});

// search handler
$app->get('/search/:query', function ($query) use ($app, $config) {
  try {
    // execute search on Nutritionix API
    // specify search scope and required response fields
    // replace with your API credentials
$qs = http_build_query(array('appId' => $config["nutritionix"]["appId"], 
 'appKey' => $config["nutritionix"]["appKey"], 'item_type' => '3', 
            'fields' => 'item_name,brand_name,nf_calories'));
$url = 'https://api.nutritionix.com/v1_1/search/' . 
              str_replace(' ', '+', $query) . '?' . $qs;
    $ch = curl_init();    
    curl_setopt($ch, CURLOPT_URL, $url);
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);  
    curl_exec($ch);
    curl_close($ch);
  } catch (Exception $e) {
    $app->response()->status(400);
    $app->response()->header('X-Status-Reason', $e->getMessage());
  }
});

$app->run();

此脚本(需求保存为 $APP_ROOT/index.php)首要加载 Slim 库,初始化并配备一个新的 Slim 运用程序目标。具体来讲,Slim 运用程序目标有必要配备 jQuery Mobile 页面模板的途径,以便能够根据需求出现这些模板。

Slim 界说了对 HTTP 办法和端点的路由器回调。为了履行此操作,它调用了相应的办法(例如 get() 来处理 GET 恳求,或许调用 post() 来处理 POST 恳求),并将 URL 路由,使之与该办法的第一个参数匹配。该办法的第二个参数是一个函数,它指定了在路由与一个传入的恳求匹配时应采纳的操作。前面的代码清单设置了两个这样的路由器回调:/index 和 /search。

  • /index 徽标:出现首要运用程序页面模板。此回调包括各种选项卡、jQuery Mobile 页面元素和 AngularJS 操控器代码。
  • /search 回调:处理 AngularJS 操控器发送的 Ajax 查找恳求。它承受一个查找词汇,然后运用 PHP http_build_query() 办法结构一个对 Nutritionix API 的恳求的 URL。该恳求经过 cURL 发送到该 API,呼应作为一个 JSON 文档回来给运用程序前端。然后,AngularJS 担任解析呼应数据并将它们绑定到该规模。

也能够直接从运用程序前端运用 AngularJS 运转 Ajax 恳求。可是,履行此操作会向用户露出您的私有 Nutritionix API 运用程序密钥,关于可揭露拜访的运用程序,不引荐这样做。运用一段端脚原本履行恳求会增加一些开支,但具有更高的安全性。

第 5 步. 核算和存储用餐记载

完结查找界面后,下一步是构建用户界面中的第二个选项卡。此选项卡显现了用户选定的食物的列表,以及这些食物中的卡路里总量。它包括一些将用餐记载保存到数据库中的控件。运用以下代码。

<!DOCTYPE html>
…
<head>
  <script>
  var myApp = angular.module('myApp', []);
  
  function myAppController($scope, $http) {

    // related to record functionality
    $scope.removeFromMeal = function(index) {
       $scope.mealItems.splice(index, 1);
    };    
    
    $scope.clearMeal = function() {
      $scope.mealItems.length = 0;
    };
    
    $scope.getTotalCalories = function() {
      var sum = 0;
      for(i=0; i<$scope.mealItems.length; i++) {
        sum += $scope.mealItems[i].fields.nf_calories;
      }
      return sum.toFixed(2);
    };    
    
    $scope.record = function() {
      if ($scope.getTotalCalories() > 0) {
        $http({
            method: 'POST',
            url: '/record',
            data: {'totalCalories': $scope.getTotalCalories()}
          }).
          success(function(data) {
            $scope.clearMeal();
          });
        };   
    };
  }
  </script>
</head>
<body>
...
    <div data-role="content" ng-app="myApp">	
      <div data-role="tabs" ng-controller="myAppController">

        <div data-role="navbar">
          <ul>
            <li><a href="#search" data-theme="a" class="ui-btn-active">Search</a></li>
            <li><a href="#record" data-theme="a">Record <span class="ui-li-count"> {{ getTotalCalories() }} / {{ mealItems.length }}</span></a></li>
            <li><a href="#report" data-theme="a">Report</a></li>
          </ul>
        </div>

        <div id="record">
          <h2 class="ui-bar ui-bar-a">Meal Record</h2>
          <div class="ui-body">
            <ul data-role="listview" data-split-theme="d">
              <li ng-repeat="item in mealItems track by $index">
                <a>{{item.fields.item_name}} / {{item.fields.nf_calories + ' calories'}}</a>
<a href="#" data-inline="true" data-role="button" data-icon="minus" 
                  data-theme="a" ng-click="removeFromMeal($index)">Add</a>
              </li>
            </ul>
          </div>          
          <div class="ui-body">
            <button ng-click="record()">Save</button>
          </div>
        </div>

      </div>
      
    </div>
…
</body>
</html>

下一个屏幕截图显现了该界面的实践外观。

运用PHP、MySQL和AngularJS构建移动友爱卡路里计数器

经过迭代 第 4 步 中的 mealItems 数组来生成选定食物列表十分简略。在用户挑选查找界面中的新食物时,AngularJS 数据绑定机制可保证该列表会即时更新。

完结用餐记载后,用户单击 Save 将该记载保存到数据库中。record() 函数创立了一个对 /record 端点的 Ajax POST 恳求,将它传递给用餐记载的卡路里总量中。假如该 Ajax 恳求被成功处理,那么 mealItems 数组将会被铲除,预备存储下一次用餐的记载。

在上一个清单中,请注意别的两点:

  • 导航栏包括两个计数器:所选食物的卡路里总量和食物总数。当用户在用餐记载中增加和删去食物时,该计数会主动更新。此更新相同是运用数据绑定来完结的。这两个值可在 mealItems 数组长度和 getTotalCalories() 操控器办法中进行动态更新。
  • 用户可单击每个食物周围的按钮,从用餐记载中删去选定的食物。此操作会调用 removeFromMeal() 操控器办法,该办法运用所选食物的索引来从 mealItems 数组中删去它。数据绑定担任更新食物和导航栏计数器。

在端,需求增加一个对 /record 端点的 Slim 回调。您或许现已猜到,此回调将会读取运用程序前端发送的卡路里总量,并将它耐久保存在 第 1 步 中创立的 MySQL 数据库中。该回调的代码为:

<?php
// use Composer autoloader
require 'vendor/autoload.php';
\Slim\Slim::registerAutoloader();

// configure credentials
// ... for Nutritionix
$config["nutritionix"]["appId"] = 'APP-ID';
$config["nutritionix"]["appKey"] = 'APP-KEY';
// ... for MySQL
$config["db"]["name"] = 'test';
$config["db"]["host"] = 'localhost';
$config["db"]["port"] = '3306';
$config["db"]["user"] = 'root';
$config["db"]["password"] = 'guessme';

// if Bluemix VCAP_SERVICES environment available
// overwrite with credentials from Bluemix
if ($services = getenv("VCAP_SERVICES")) {
  $services_json = json_decode($services, true);
  $config["db"] = $services_json["mysql-5.5"][0]["credentials"];
}

// configure Slim application instance
$app = new \Slim\Slim();
$app->config(array(
  'debug' => true,
  'templates.path' => './templates'
));

// initialize PDO object
$db = $config["db"]["name"];
$host = $config["db"]["host"];
$port = $config["db"]["port"];
$username = $config["db"]["user"];
$password = $config["db"]["password"];  
$dbh = new PDO("mysql:host=$host;dbname=$db;port=$port;charset=utf8", $username, $password);

// start session
session_start();

// record handler
$app->post('/record', function () use ($app, $dbh) {
  try {
    // get and decode JSON request body
    $request = $app->request();
    $body = $request->getBody();
    $input = json_decode($body);

    // insert meal record
    $stmt = $dbh->prepare('INSERT INTO meals (uid, calories, rdate, ip) VALUES(?, ?, ?, ?)');
$stmt->execute(array($_SESSION['uid'], $input->totalCalories, 
      date('Y-m-d h:i:s', time()), $_SERVER['SERVER_ADDR']));
    $input->id = $dbh->lastInsertId();
    
    // return JSON-encoded response body
    $app->response()->header('Content-Type', 'application/json');
    echo json_encode($input);    
  } catch (Exception $e) {
    $app->response()->status(400);
    $app->response()->header('X-Status-Reason', $e->getMessage());
  }  
});

// snip: other handlers

$app->run();

此进程首要配备本地运用程序数据库的凭证。然后在 PHP 环境中查看特别的 VCAP_SERVICES 环境变量。在 Bluemix 上,此变量具有绑定的服务实例的凭证。假如找到此变量,该脚本会假定它在 Bluemix 上运转,运用这些凭证来初始化一个与绑定的 MySQL 实例的 PDO 衔接。假如未找到此变量,该脚本会假定它在一个本地开发实例上运转,并运用本地数据库的凭证。

接下来,该清单为 /record 路由界说了一个 POST 回调处理函数。此处理函数收到包括卡路里总量的 Ajax POST 恳求,并创立 SQL INSERT 句子来将这些计数保存到数据库中。除了卡路里计数之外,该处理函数还会在句子中主动增加时刻戳、客户端的 IP 地址和登录用户的专一标识符。假如 INSERT 成功完结,该处理函数会向恳求的 Ajax 脚本回来一个包括记载标识符的 JSON 包。

您或许想知道用户标识符来自何处。这将在 第 7 步 中具体介绍,但简略地讲,每个运用程序用户都有一个在注册时生成的专一标识符。用户登录时,此标识符会被增加到 $_SESSION['uid'] 变量中的会话中,并在您保存和检索特定于用户的信息时刺进到各种 SQL 句子中。

咱们感兴趣的内容
小同伴独爱的新闻
小同伴还重视了以下信息
小同伴重视的焦点

小同伴都在重视的抢手词

新服 缤纷活动 帆海世纪 芈月传 暗黑道具 萌乐网 苹果发布会 最新谍照 三国令 剑雨江湖 怎样修炼战骑 页游 怎样修炼同伴 木甲国际 仙侠道2 推黑科技 页游形式 武圣试炼场 街机玩法 蓝月传奇 个人BOSS玩法 哥们网 九阴绝学 仗剑出鞘 全新形式 范伟打天下 全新元神玩法 七大神兵简介 新手攻略 跑腿使命 门派五行 城战礼包 页游界 泥石流 傅园慧 经典网页游戏 耐玩 盘点 玉石攻略 提高人物 大黑 实装特点 神兵攻略 闻名莽荒 莽荒纪 手持神兵 土豪梦 万世 开学清单 财富赚不断 天书国际 大黑游戏 资源战场 ppwan 天问 激战 全国大战 雄霸一方 新增宠物技术 肯定小能手 花千骨 三尾章鱼 风色轨道 双枪手 弑之神 缤纷好礼 惊喜六重连 帮会 中秋福利 克己月饼 九阴真经 玩家 五周年留念 留念银币 名动三界 新服资料片 画江山 勇战妖魔 邪恶势力 上古降魔 老司机玩法 坐骑揭秘 黑科技 竞技场攻略 铁血皇城 披风玩法 书剑恩仇录 配备强化攻略 户外BOSS玩法 全网曝光 赤壁传说 半回合制国 ACT 奇珍商城 热血战歌 传奇瑰宝抽奖 翻开方式 门徒 门徒获取玩法 三大萌宠简介