创建了应用的基础之后,我们允许用户注册并登录到我们的应用。这是任何应用的很大一部分,但我们仍然缺少创建可以连接到用户帐户的内容。我们将在本章中通读所有内容!

在本章中,我们将:

  • 创建用户配置文件以公开显示用户信息
  • 使用引导清理配置文件
  • 处理各种异常
  • 讨论 CouchDB 中 post 和关系的建模
  • 创建一个表单,从登录用户的配置文件创建帖子

在为我们规划好道路之后,让我们继续讨论用户配置文件!

用户档案

任何社交网络的主要吸引力在于用户的个人资料;用户配置文件通常显示用户的基本信息,并显示他们创建的任何内容。

本节结束时,我们的用户配置文件将按如下方式工作:

  • 如果访客前往 http://localhost/verge/user/johndoe,我们的路由系统会将其与 /user/:username路由匹配
  • index.php文件将 johndoe作为 username的值,并将其传递给 User类,试图找到具有匹配 ID 的用户文档
  • 如果找到 johndoe,则 index.php将显示一个配置文件,其中包含 johndoe的信息
  • 如果没有找到 johndoe,访问者将看到 404错误,这意味着具有该用户名的用户不存在

查找具有路由的用户

为了找到用户,我们首先需要创建一个函数,该函数将用户名作为参数,并返回一个有效的用户对象。

行动时间-获取单用户文档

您可能还记得,在第 3 章中开始使用 CouchDB 和 Futon时,我们可以通过传递所需文档的 ID 从 CouchDB 检索文档。这次我们也将使用 Sag 查找用户信息。需要注意的一点是,我们必须确保在查找使用 ID 的用户时,我们需要在其前面加上 org.couchdb.user:名称空间。

让我们先打开 classes/user.php并滚动到底部。

  1. 添加一个名为 get_by_username()的 public static函数。
    public static function get_by_username() {
    }

    
  1. 为了通过 ID 查找用户,我们需要允许我们的函数接受参数 $username。
    public static function get_by_username($username = null) {
    }

    
  1. 现在,让我们将数据库设置为实例化骨骼和代理 Sag。记住,我们处理的是 _users数据库,所以我们需要以 admin权限登录。
    public static function get_by_username($username = null) {
    $bones = new Bones();
    $bones->couch->setDatabase('_users');
    $bones->couch->login(ADMIN_USER, ADMIN_PASSWORD);
    }

    
  1. 现在我们可以连接到 _users数据库,让我们通过 Sag 发出 get调用,通过在传递的用户名中添加 org.couchdb.user:返回用户。
    public static function get_by_username($username = null) {
    $bones = new Bones()
    $bones->couch->login(ADMIN_USER, ADMIN_PASSWORD);
    $bones->couch->setDatabase('_users');
    $user = new User();
    $document = $bones->couch->get('org.couchdb.user:' . $username)- >body;
    $user->_id = $document->_id;
    $user->name = $document->name;
    $user->email = $document->email;
    $user->full_name = $document->full_name;
    return $user;
    } 

    

刚才发生了什么事?

我们创建了一个名为 get_by_username的 public static函数,允许我们传入 $username。为了实际获取文档,我们需要使用我们的 ADMIN_USER和 ADMIN_PASSWORD常量来访问 _users数据库。为了让我们返回一个用户对象,我们需要创建一个名为 $user的新用户对象。然后,我们使用 Sag 的 get调用通过 ID 识别文档,并将其作为名为 $document的 stdClass对象返回。然后我们从 document变量中获取值,并将它们传递到 $user对象上的相应值中。最后,我们将用户文档返回到调用函数的任何位置。

现在我们有了一个通过用户名查找用户的函数,让我们在 index.php中创建一个路由,将用户名传递给这个函数。

行动时间-为用户配置文件创建路由

我们将创建一个路由,以便人们可以通过访问唯一的 URL 来查看配置文件。这将是我们第一次真正利用路由系统处理路由变量的能力。

  1. 打开 index.php,输入以下代码为用户档案创建 get路由:
    get('/user/:username', function($app) {
    });

    
  1. 让我们使用路由变量 :username告诉我们要查找的用户名;我们将把它传递给我们在 User类中创建的 get_by_username函数。最后,我们将返回的 user对象传递给 user变量
    get('/user/:username', function($app) {
    $app->set('user', User::get_by_username($app- >request('username'))); 
    });

    

中的视图

  1. 最后,我们将渲染 user/profile.php视图,我们将很快创建该视图。
    get('/user/:username', function($app) {
    $app->set('user', User::get_by_username($app- >request('username')));
    $app->render('user/profile'); 
    });

    

刚才发生了什么事?

我们只用了四行代码就做了很多!首先,我们使用 route /user/:username定义了用户配置文件路由。接下来,我们在 user类中创建了一段代码,将 :username从 route变量传递到 get_by_username函数。 get_by_username函数将返回一个包含用户信息的对象,我们使用 $app->set('user')将其发送到视图。最后,我们呈现了用户配置文件。

让我们继续创建用户配置文件,以便我们可以看到我们的辛勤工作在行动!

行动时间-创建用户配置文件

在本章中,我们将对 user视图进行几次清理。但是,让我们首先将所有用户文档内容转储到视图中。

  1. 在我们的 working文件夹的 views目录中创建一个名为 user/profile.php的视图。
  2. 使用以下 HTML 为概要文件创建一个简单的标题:
    <div class="page-header">
    <h1>User Profile</h1>
    </div>

    
  1. 既然我们还没有太多的设计,我们就用 var_dump来显示 User文档的所有内容:
    <div class="page-header">
    <h1>User Profile</h1>
    </div>
    <div class="container">
    <div class="row">
    <?php var_dump($user); ?>
    </div>
    </div> 

    

刚才发生了什么事?

我们刚刚创建了一个非常基本的用户配置文件,带有一个标题,告诉我们这个页面就是用户配置文件。然后,我们使用 var_dump显示 user对象的所有内容。 var_dump是一个通用 PHP 函数,用于输出有关变量或对象的结构化信息,在您只想确保事情正常运行时非常有用。

测试一下

现在我们有了一个简单的用户配置文件设置,让我们看看结果如何。

  1. 打开浏览器,进入 http://localhost/verge/user/johndoe。
  2. Your browser will show you the following:

Testing it out

  • 不太破旧,但我们当然需要尽快清理这些数据的格式。但是,现在,让我们确保将我们的更改提交给 Git。

将您的更改添加到 Git

在本节中,我们开始创建一个用户配置文件,并直接从 CouchDB 输出用户信息。让我们将所有更改添加到 Git 中,以便跟踪进度。

  1. 打开终端。
  2. 键入以下命令将目录更改为我们的工作目录。
    cd /Library/Webserver/Documents/verge/ 

    
  1. 我们只添加了一个文件, views/user/profile.php,所以让我们告诉 Git 将这个文件添加到源代码管理中。
    git add views/user/profile.php 

    
  1. 描述一下我们自上次犯罪以来所做的事情。
    git commit am 'Created the get_by_username function, a basic user profile, and a route to display it' 

    

解决一些问题

您可能已经注意到,我们忽略了用户配置文件的一个潜在问题,我们没有很好地处理找不到用户配置文件时发生的情况。

例如:

如果您要转到 http://localhost/verge/user/someone,您的浏览器将显示以下非常不友好的错误消息:

Fixing some problems

发现错误

在第 6 章中,我们通过终端使用 tail命令查看 Apache 的错误日志。我们将再次做同样的事情。让我们看一下 Apache 的日志,看看我们能不能弄清楚到底出了什么问题。

行动时间-检查 Apache 日志

在第 6 章中,我们试图首先找到我们的 Apache 日志。默认保存在 /private/var/log/apache2/error_log处。如果在上一章中您发现它位于其他地方,您可以通过键入 grep ErrorLog /etc/apache2/httpd.conf再次找到它的位置。

让我们找出问题所在。

  1. 打开终端。
  2. 通过运行以下命令检索日志的最后几行:
    tail /private/var/log/apache2/error_log 

    
  1. 日志会告诉你很多事情,但最重要的信息是这一条,上面写着 PHP Fatal error。你的可能略有不同,但整体信息是相同的。
    [Wed Sep 28 09:29:49 2011] [error] [client 127.0.0.1] PHP Fatal error: Uncaught exception 'SagCouchException' with message 'CouchDB Error: not_found (missing)' in /Library/WebServer/Documents/verge/lib/sag/src/Sag.php:1221\nStack trace:\n#0 /Library/WebServer/Documents/verge/lib/sag/src/Sag.php(206): Sag->procPacket('GET', '/_users/org.cou...')\n#1 /Library/WebServer/Documents/verge/classes/user.php(81): Sag->get('org.couchdb.use...')\n#2 /Library/WebServer/Documents/verge/index.php(44): User::get_by_username('someone')\n#3 /Library/WebServer/Documents/verge/lib/bones.php(91): {closure}(Object(Bones))\n#4 /Library/WebServer/Documents/verge/lib/bones.php(13): Bones::register('/user/:username', Object(Closure), 'GET')\n#5 /Library/WebServer/Documents/verge/index.php(46): get('/user/:username', Object(Closure))\n#6 {main}\n thrown in /Library/WebServer/Documents/verge/lib/sag/src/Sag.php on line 1221 

    

刚才发生了什么事?

我们使用 tail命令返回 Apache 日志的最后几行。如果你仔细看日志,你会看到 CouchDB error。更具体地说,错误如下:

error: Uncaught exception 'SagCouchException' with message 'CouchDB Error: not_found (missing)' 

这条消息意味着 CouchDB 对我们所做的不满意,Sag 以 SagCouchException的形式抛出了一个错误。为了正确处理 SagCouchException,我们需要在对 Sag 的调用中添加一些代码。

在上一章中,我们通过检查状态代码并将其与解析匹配来修复错误。我们可以继续这样做,但最终会出现我们不知道的错误。从现在开始,当发生未处理的异常时,我们希望显示一条友好的错误消息,以便对其进行调试。

在下一节中,我们将使用 Bones 来帮助我们显示异常页面。

处理 500 个错误

我们真正想要解决的是如何处理应用中的 500 个错误。500 错误指 HTTP 状态码 500,为内部服务器错误。一般来说,这意味着发生了一些事情,我们没有妥善处理。

行动时间-用骨骼处理 500 个错误

让我们先创建一个简单的视图,向我们显示错误。

  1. 让我们首先在 views目录中创建一个名为 error的新文件夹。
  2. 创建一个名为 500.php的新视图,并将其放入 errors文件夹(views/error/500.php)。
  3. 将以下代码添加到异常的 500.php输出信息中:
    <div class="hero-unit">
    <h1>An Error Has Occurred</h1>
    <p>
    <strong>Code:</strong><?php echo $exception->getCode(); ?>
    </p>
    <p>
    <strong>Message:</strong>
    <?php echo $exception->getMessage(); ?>
    </p>
    <p><strong>Exception:</strong> <?php echo $exception; ?></p>
    </div>

    
  1. 在 lib/bones.php中添加一个名为 error500的函数,它允许我们在应用周围轻松显示 500 个错误。
    public function error500($exception) {
    $this->set('exception', $exception);
    $this->render('error/500');
    exit;
    }

    

刚才发生了什么事?

我们在 views目录中创建了一个名为 error的新文件夹,它将包含我们将在应用中使用的所有错误视图。然后我们创建了一个名为 500.php的新视图,以友好的方式显示异常。异常是 Sag 使用 SagCouchException类扩展的内置类。这样,我们就很容易在视图中直接与这个异常类对话。这个 Exception类有很多属性。但是,在这个应用中,我们只需要显示代码、消息和异常,以字符串格式表示。最后,我们在 Bones 中创建了一个函数,允许我们传入异常,以便在视图中显示它。在这个函数中,我们将异常传递给 error/500视图,然后使用 exit,它告诉 PHP 停止在应用中执行任何其他操作。这会阻止我们的应用执行任何其他操作,因为出现了问题。

行动时间-处理异常

现在我们可以处理异常了,让我们为 get_by_username函数添加一些代码,以便我们可以更深入地研究我们的问题。

  1. 让我们打开 classes/user.php,并在 Sag 调用周围添加 try...catch语句,以确保我们能够处理发生的任何错误。
    public static function get_by_username($username = null) {
    $bones = new Bones();
    $bones->couch->login(ADMIN_USER, ADMIN_PASSWORD);
    $bones->couch->setDatabase('_users');
    $user = new User();
    try { 
    $document = $bones->couch->get('org.couchdb.user:' . $username)->body;
    $user->_id = $document->_id;
    $user->name = $document->name;
    $user->email = $document->email;
    $user->full_name = $document->full_name;
    return $user;
    } catch (SagCouchException $e) {
    } 
    }

    
  1. 现在我们正在捕获错误,让我们添加 error500函数。
    public static function get_by_username($username = null) {
    $bones = new Bones();
    $bones->couch->login(ADMIN_USER, ADMIN_PASSWORD);
    $bones->couch->setDatabase('_users');
    $user = new User();
    try {
    $document = $bones->couch->get('org.couchdb.user:' . $username)->body;
    $user->_id = $document->_id;
    $user->name = $document->name;
    $user->email = $document->email;
    $user->full_name = $document->full_name;
    return $user;
    } catch (SagCouchException $e) {
    $bones->error500($e); 
    }
    }

    
  1. 在 classes/user.php中,让我们捕捉更多可能的例外情况。让我们从 public函数注册开始。
    public function signup($username, $password) {
    $bones = new Bones();
    $bones->couch->setDatabase('_users');
    $bones->couch->login(ADMIN_USER, ADMIN_PASSWORD);
    $this->roles = array();
    $this->name = preg_replace('/[^a-z0-9-]/', '', strtolower($username));
    $this->_id = 'org.couchdb.user:' . $this->name;
    $this->salt = $bones->couch->generateIDs(1)->body->uuids[0];
    $this->password_sha = sha1($password . $this->salt);
    try {
    $bones->couch->put($this->_id, $this->to_json());
    }
    catch(SagCouchException $e) {
    if($e->getCode() == "409") {
    $bones->set('error', 'A user with this name already exists.');
    $bones->render('user/signup');
    } else {
    $bones->error500($e);
    } 
    }
    }

    
  1. 接下来,让我们在公共函数登录中添加 catch语句。
    public function login($password) {
    $bones = new Bones();
    $bones->couch->setDatabase('_users');
    try {
    $bones->couch->logiBn($this->name, $password, Sag::$AUTH_COOKIE);
    session_start();
    $_SESSION['username'] = $bones->couch->getSession()->body- >userCtx->name;
    session_write_close();
    }
    catch(SagCouchException $e) {
    if($e->getCode() == "401") {
    $bones->set('error', 'Incorrect login credentials.');
    $bones->render('user/login');
    exit;
    } else {
    $bones->error500($e);
    } 
    }
    }

    

刚才发生了什么事?

现在我们可以优雅地处理异常了,我们浏览了 User类,并添加了在发生意外事件时抛出 500错误的功能。在我们已经预料到某些问题的呼叫中,如果发生意外情况,我们可以使用 if...else语句触发 500错误。

测试我们的异常处理程序

让我们再试一次,看看是否能够找到异常的根源。

  1. 转到 http://localhost/verge/user/someone。
  2. You will now see a much friendlier error page, which tells us the code, message, and the full error that you would see in the error logs.

Testing our exception handler

对我们来说,从这件事上搞清楚到底发生了什么要容易得多。当我们在调试应用的过程中,这个页面对于我们跟踪正在发生的错误非常有用。

通过查看这段代码,我们可以看出 CouchDB 向我们抛出了一个 404错误。我们可能希望发生此错误,因为我们正在查找不存在的用户文档。让我们进一步探究什么是 404错误以及我们如何处理它。

显示 404 错误

404错误是指 HTTP 状态码 404,表示“未找到”。当您尝试访问不存在的内容时,通常会发生 404错误,例如访问错误的 URL。在我们的例子中,我们收到一个 404错误,因为我们试图找到一个不存在的 CouchDB 文档。

404 如果找不到用户

404错误是一种特殊的错误,我们会在应用的不同位置看到。让我们创建另一个错误页面,无论何时发生 404错误,我们都可以使用它。

行动时间:用骨骼处理 404 个错误

让我们为 404错误创建一个视图,以便在应用中使用。

  1. 首先在我们的 views/error/目录中创建一个名为 404.php的新视图。
  2. 让我们在 404.php中添加一些非常基本的代码,通知访问者我们的应用无法找到请求的页面。
    <div class="hero-unit">
    <h1>Page Not Found</h1>
    </div>

    
  1. 为了呈现这个视图,让我们在 lib/bones.php文件中添加另一个名为 error404的函数。此函数将很好地为我们显示 404错误。
    public function error404() {
    $this->render('error/404');
    exit;
    }

    

刚才发生了什么事?

我们创建了一个名为 404.php的简单视图,可以在应用中发生 404错误时随时显示该视图。然后,我们在 lib/bones.php中创建了一个名为 error404的简单函数,用于呈现 error/404.php并终止当前脚本,因此不会发生进一步的操作。

为未知用户显示 404 错误

现在我们有了 404错误处理程序,当 classes/user.php中的 get_by_username函数发生 404错误时,我们来显示它。

打开 classes/user.php,修改 get_by_username功能,使之与以下内容相匹配:

public static function get_by_username($username = null) {
$bones = new Bones();
$bones->couch->login(ADMIN_USER, ADMIN_PASSWORD);
$bones->couch->setDatabase('_users');
$user = new User();
try { 
$document = $bones->couch->get('org.couchdb.user:' . $username)- >body;
$user->_id = $document->_id;
$user->name = $document->name;
$user->email = $document->email;
$user->full_name = $document->full_name;
return $user;
} catch (SagCouchException $e) {
if($e->getCode() == "404") {
$bones->error404();
} else {
$bones->error500($e);
} 
}
}

连接现场周围的 404

404错误的有趣之处在于,每当游客经过 Bones 不理解的路线时,错误都会发生。因此,让我们将代码添加到骨骼中,以便为我们处理此问题。

行动时间-用骨骼处理 404 错误

让我们在 lib/bones.php和 index.php周围添加一些简单的代码,以允许我们处理 404错误。

  1. 打开 lib/bones.php,在 Bones类中创建一个名为 resolve的函数,我们可以在路由结束时调用该函数,并确定是否找到了路由。
    public static function resolve() {
    if (!static::$route_found) {
    $bones = static::get_instance();
    $bones->error404();
    }
    }

    
  1. 转到 lib/bones.php的顶部,在 Bones类之外创建一个名为 resolve的函数(例如 get, post, put或 delete),我们可以在任何地方调用它。
    function resolve() {
    Bones::resolve();
    }

    
  1. 我们要做的就是在 index.php的最底部添加一行代码,如果没有找到路由,就可以调用该代码。添加更多路由时,请确保 resolve()始终位于文件末尾。
    get('/user/:username', function($app) {
    $app->set('user', User::get_by_username($app- >request('username')));
    $app->render('user/profile');
    });
    resolve(); 

    

刚才发生了什么事?

我们创建了一个名为 resolve的函数,该函数在所有路由之后在 index.php文件的底部执行。此功能用作“清理up”功能,如果没有匹配的路由,将执行该功能。如果没有匹配的路由,resolve 将向访问者显示一个 404错误并终止当前脚本。

测试一下

现在我们已经优雅地处理了 404错误,让我们测试一下,看看会发生什么。

  1. 打开浏览器,进入 http://localhost/verge/user/anybody。
  2. Your browser will show you the following:

Testing it out

  • 伟大的我们的User类正在向我们转发一个404错误,因为我们在get_by_username函数中添加了代码。

Testing it out

完美的我们的404错误处理程序正按照我们需要的方式工作。如果我们需要再次使用它,我们所需要做的就是在我们的Bones课程中调用error404,我们都准备好了!

为用户提供其个人资料的链接

在大多数社交网络中,登录后,会显示一个链接,以查看当前登录的用户配置文件。让我们打开 view/layout.php并在导航中添加 My Profile链接。

<ul class="nav">
<li><a href="<?php echo $this->make_route('/') ?>">Home</a></li>
<?php if (User::is_authenticated()) { ?> <li>
<a href="<?php echo $this->make_route('/user/' . User::current_user()) ?>">
My Profile
</a>
</li>
<li>
<a href="<?php echo $this->make_route('/logout') ?>">
Logout
</a>
</li>
<?php } else { ?>
<li>
<a href="<?php echo $this->make_route('/signup') ?>">
Signup
</a>
</li>
<li>
<a href="<?php echo $this->make_route('/login') ?>">
Login
</a>
</li>
<?php } ?>
</ul>

使用引导创建更好的配置文件

我开始担心,我们的个人资料没有很好地组合在一起,我们需要在本章后面的内容中进一步补充。让我们准备我们的用户配置文件,这样我们就可以很好地显示用户的信息和帖子。

行动时间-检查用户当前是否登录

我们需要弄清楚用户查看的配置文件是否是他们自己的。那么,让我们在视图中添加一个变量,告诉我们是否是这样。

  1. 打开 index.php,添加一个名为 is_current_user的变量,该变量将确定您正在查看的配置文件是否与当前登录的用户相同。
    get('/user/:username', function($app) {
    $app->set('user', User::get_by_username($app- >request('username')));
    $app->set('is_current_user', ($app->request('username') == User::current_user() ? true : false)); 
    $app->render('user/profile');
    });

    
  1. 让我们更改 views/user/profile.php标题中的代码,这样我们可以输出用户的全名以及 This is you!,如果它是当前用户的配置文件。
    <div class=-page-header->
    <h1><?php echo $user->full_name; ?>
    <?php if ($is_current_user) { ?>
    <code>This is you!</code>
    <?php } ?>
    </h1> 
    </div>

    

刚才发生了什么事?

我们使用了一种叫做 ternary的速记操作。 ternary操作是 if-else语句的简写。在这种情况下,我们的意思是,如果从路由传递的用户名等于当前登录用户的用户名,则返回 true,否则返回 false。然后,如果 is_current_user变量设置为 true,我们进入我们的配置文件并显示 This is you!。

清理外形设计

同样,Bootstrap 将通过允许我们用有限的代码清理我们的配置文件来拯救我们。

  1. 首先,我们将行 div拆分为两列,代码如下:
    <div class="page-header">
    <h1><?php echo $user->full_name; ?>
    <?php if ($is_current_user) { ?>
    <code>This is you!</code>
    <?php } ?>
    </h1>
    </div>
    <div class="container">
    <div class="row">
    <div class="span4">
    <div class="well sidebar-nav">
    <ul class="nav nav-list">
    <li><h3>User Information</h3>
    </ul>
    </div>
    </div>
    <div class="span8">
    <h2>Posts</h2>
    </div>
    </div>
    </div> 

    
  1. 让我们通过在无序列表中添加更多列表项,将用户信息输出到左列。
    <div class="container">
    <div class="row">
    <div class="span4">
    <div class="well sidebar-nav">
    <ul class="nav nav-list">
    <li><h3>User Information</h3></li>
    <li><b>Username:</b> <?php echo $user->name; ?></li>
    <li><b>Email:</b> <?php echo $user->email; ?></li> 
    </ul>
    </div>
    </div>
    <div class="span8">
    <h2>Posts</h2>
    </div>
    </div>
    </div>

    

让我们看看我们的新个人资料

有了这一点,我们新的和改进的个人资料变得栩栩如生!我们来看看。

  1. 进入 http://localhost/verge/user/johndoe打开您的浏览器至 johndoe用户的 URL。
  2. Your browser will show you a nicely remodeled user profile.

Let's check out our new profile

  1. 现在,让我们检查一下,以确保我们的 $is_current_user变量也正常工作。为此,请使用 johndoe作为用户名登录,然后转到 http://localhost/verge/user/johndoe。
  2. Your browser will show you the user profile, along with a nice message telling you that this is your profile.

Let's check out our new profile

令人惊叹的我们的个人资料真的开始走到一起了。这是我们应用中的一个重大里程碑。所以,让我们确保将更改提交给 Git。

将您的更改添加到 Git

在本节中,我们添加了对干净地处理异常的支持,并改进了用户配置文件。让我们将所有更改添加到 Git,以便跟踪进度。

  1. 打开终端。
  2. 键入以下命令将目录更改为我们的 working目录:
    cd /Library/Webserver/Documents/verge/ 

    
  1. 我们在本节中添加了一些文件。那么,让我们将它们全部添加到源代码管理中。
    git add . 

    
  1. 向 Git 描述自上次提交以来我们所做的工作。
    git commit -am 'Added 404 and 500 error exception handling and spruced up the layout of the user profile' 

    

岗位

我们的个人资料上有一个帖子的占位符。但是,让我们用一些真实的内容来填充它。我们将通过允许用户发布小部分内容并将其绑定到用户帐户来实现这一点。

模特岗

让我们讨论一下,为了将帖子保存到 CouchDB 并将其与用户关联,需要做些什么。在讨论如何使用 CouchDB 之前,让我们先来看看如何使用 MySQL 来实现它。

如何在 MySQL 中建模帖子

如果我们为 MySQL(或其他 RDBMS)建模这种关系,它可能看起来类似于下面的屏幕截图:

How to model posts in MySQL

简而言之,此图显示了一个 posts表,该表有一个引用 users 表 id的外键 user_id。这种一对多关系在大多数应用中都很常见,在本例中,这意味着一个用户可以有多篇文章。

现在我们已经看了一个熟悉的图表,让我们看一下与 CouchDB 相关的关系。

如何在 CouchDB 中建模帖子

令人惊讶的是,CouchDB 以一种非常相似的方式处理关系。你可能在想,等等,我以为你说的不是关系数据库。请记住,无论您使用的是什么数据库,它们处理关系的方式都可能有共性。让我们看看 CouchDB 将如何演示相同的数据和模型。

How to model posts in CouchDB

这很相似,对吧?与以往一样,最大的区别在于,在关系数据库中,数据存储在集合行和列中,而在 CouchDB 中,数据存储在自包含文档中,具有一组无模式的键和值。无论您以何种方式查看数据,关系都保持不变,也就是说,post 通过引用用户的 ID 连接到该用户。

为了确保我们在同一页上,让我们浏览一下 post文档中的每个字段,确保我们理解它们是什么。

  • _id是文档的唯一标识符。
  • _rev为文件的修订标识。如果你想重新审视这个概念,我们在第 3 章中提到了修订。
  • type告诉我们正在查看哪种文档。在这种情况下,每个 post文档将等于 post。
  • date_created是创建文档的时间戳。
  • content包含我们希望在帖子中包含的任何文本。
  • user包含创建帖子的用户的用户名,并引用 _users文档。有趣的是,我们不需要将 org.couchdb.user放在这个字段中,因为 CouchDB 实际上要查看用户名。

现在我们已经定义了需要保存到 CouchDB 的值,我们准备在一个新类中对其建模: Post。

有一个围棋英雄-设置 Post 类

创建 Post类将非常类似于我们的 User类。如果你足够自信,试着自己创建基本类。

以下是您需要做的:

  1. 创建一个名为 post.php的新类,该类扩展了 Base类。
  2. 为前面定义的每个必填字段创建变量。
  3. 增加 construct函数定义单据类型。

完成后,继续阅读下一页,确保你的作品与我的作品一致。

让我们看看结果如何。

您应该创建一个名为 post.php的新文件,并将其放置在我们 working文件夹的 classes目录中。 post.php的内容应类似于以下内容:

<?php
class Post extends Base
{
protected $date_created;
protected $content;
protected $user;
public function __construct() {
parent::__construct('post');
}
}

这就是我们在 PHP 中处理 post 文档所需的全部内容。现在我们已经构建了这个类,让我们继续创建帖子。

创建帖子

现在,创建帖子对我们来说将是小菜一碟。我们所需要做的就是添加几行代码,然后它就会放在数据库中。

行动时间-制作一个处理后期创建的函数

让我们创建一个名为 create的公共函数,它将为我们的应用创建帖子。

  1. 打开 classes/post.php,滚动至底部。在这里,我们将创建一个名为 create的新公共函数。
    public function create() {
    }

    
  1. 让我们从获取骨骼的新实例开始,然后设置当前 post对象的变量。
    public function create() {
    $bones = new Bones();
    $this->_id = $bones->couch->generateIDs(1)->body->uuids[0];
    $this->date_created = date('r');
    $this->user = User::current_user();
    } 

    
  1. 最后,让我们使用 Sag 将文档放到 CouchDB。
    public function create() {
    $bones = new Bones();
    $this->_id = $bones->couch->generateIDs(1)->body->uuids[0];
    $this->date_created = date('r');
    $this->user = User::current_user();
    $bones->couch->put($this->_id, $this->to_json()); 
    }

    
  1. 让我们用一个 try...catch语句来结束对 CouchDB 的调用,在 catch语句的内部,让我们像以前一样将其反弹到一个 500错误。
    public function create() {
    $bones = new Bones();
    $this->_id = $bones->couch->generateIDs(1)->body->uuids[0];
    $this->date_created = date('r');
    $this->user = User::current_user();
    try {
    $bones->couch->put($this->_id, $this->to_json());
    }
    catch(SagCouchException $e) {
    $bones->error500($e);
    } 
    }

    

刚才发生了什么事?

我们刚刚创建了一个名为 create的函数,它使我们能够创建一个新的 Post文档。我们首先实例化一个骨骼对象,以便使用 Sag。接下来,我们使用 Sag 抓取一个 UUID作为 post的 ID。然后,我们使用 date('r')将日期输出为 RFC 2822格式(这与 CouchDB 和 JavaScript 类似),并将其保存到 post 的 date_created变量中。然后,我们将帖子的用户设置为当前用户的用户名。

设置好所有字段后,我们使用 Sag 的 put命令将 post 文档保存到 CouchDB。最后,为了确保我们没有遇到任何错误,我们将 put命令包装在一个 try...catch语句中。在 catch部分,如果出现问题,我们将用户传递给 Bones 的 error500函数。就这样!我们现在可以在应用中创建帖子。我们所剩下的就是在我们的用户配置文件中创建一个表单。

行动时间-制作表单以启用后期创建

让我们在用户的个人资料页面中编写表单以进行后期创建。此表单仅在登录用户查看自己的个人资料时显示。

  1. 打开 user/profile.php。
  2. 让我们首先检查一下用户查看的配置文件是否是他们自己的。
    <div class="span8">
    <?php if ($is_current_user) { ?>
    <h2>Create a new post</h2>
    <?php } ?> 
    <h2>Posts</h2>
    </div>

    
  1. 接下来,让我们添加一个表单,允许当前登录的用户发表文章。
    <div class="span8">
    <?php if ($is_current_user) { ?>
    <h2>Create a new post</h2>
    <form action="<?php echo $this->make_route('/post')?>" method="post">
    <textarea id="content" name="content" class="span8" rows="3">
    </textarea>
    <button id="create_post" class="btn btn-primary">Submit
    </button>
    </form>
    <?php } ?> 
    <h2>Posts</h2>
    </div>

    

刚才发生了什么事?

我们使用 $is_current_user变量来确定查看配置文件的用户是否等于当前登录的用户。接下来,我们创建了一个发布到 post路由的表单(我们将在下一步创建)。在表单中,我们放置了一个带有 content的 id和 submit按钮的 textarea来实际发布表单。

现在我们已经准备好了一切,让我们通过在我们的 index.php文件中创建一个名为 post的路由来处理 post路由,从而完成 post的创建。

行动时间-创建路线并处理帖子的创建

为了实际创建帖子,我们需要创建一个路由并处理表单输入。

  1. 打开 index.php。
  2. 创建一条基本的 post路线,并将其命名为 post。
    post('/post', function($app) {
    });

    
  1. 在我们的 post路由中,让我们接受传递值 content的值,并使用 Post类上的 create函数来实际创建帖子。创建帖子后,我们会将用户重定向回他们的个人资料。
    post('/post', function($app) {
    $post = new Post();
    $post->content = $app->form('content');
    $post->create();
    $app->redirect('/user/' . User::current_user()); 
    });

    
  1. 我们已经做了很多工作来确保用户在创建帖子时经过身份验证,但是让我们在这里再次检查用户是否经过身份验证。如果结果表明它们没有经过身份验证,我们的应用会将它们转发到用户登录页面,并显示一条错误消息。
    post('/post', function($app) {
    if (User::is_authenticated()) { 
    $post = new Post();
    $post->content = $app->form('content');
    $post->create();
    $app->redirect('/user/' . User::current_user());
    } else {
    $app->set('error', 'You must be logged in to do that.');
    $app->render('user/login');
    } 
    });

    

刚才发生了什么事?

在本节中,我们为路径 post创建了一条 post路径(对不起,这是一个需要破译的令人困惑的句子)。在 post路由中,我们实例化了一个 Post对象,并将其实例变量 content设置为等于发布表单中的内容 textarea。接下来,我们通过调用 public create函数创建了 post。保存帖子后,我们将用户重定向回他/她自己的个人资料。最后,我们在整个 route中添加了检查功能,以确保用户已登录。如果他们没有,我们会将他们弹出到登录页面,并要求他们登录以执行此操作。

测试一下

现在,我们已经对创建帖子所需的一切进行了编程,让我们一步一步地进行测试。

  1. 首先以 johndoe的身份登录,然后打开浏览器进入 http://localhost/verge/user/johndoe进入他的个人资料。
  2. Your browser will show you a user profile just as we've seen before, but this time you'll see the post form.

Test it out

  1. 在文本区域键入一些内容。我打了 I don't like peanut butter,但你可以让它说你想说的任何话。
  2. 完成后,单击提交按钮。
  3. 您已被转发回 johndoe的用户配置文件,但您尚未看到任何帖子。所以,让我们登录 Futon 以确保该帖子已创建。
  4. 通过进入 http://localhost:5984/_utils/database.html?verge进入 Futon 中的 verge数据库。
  5. Sweet! There's a document here; let's open it up and look at the contents.

Test it out

这一切都很顺利!当用户登录时,他们可以通过转到个人资料并提交创建新帖子表单来创建帖子。

将您的更改添加到 Git

在本节中,我们添加了一个基于 Post模型创建帖子的功能。然后,我们在用户配置文件中添加了一个表单,以便用户可以实际创建帖子。让我们将所有更改添加到 Git,以便跟踪进度。

  1. 打开终端。
  2. 键入以下命令将目录更改为我们的 working目录:
    cd /Library/Webserver/Documents/verge/ 

    
  1. 我们添加了 classes/post.php文件,所以让我们将该文件添加到源代码管理中:
    git add classes/post.php 

    
  1. 描述Git自上次提交以来我们所做的工作:
    git commit –am 'Added a Post class, built out basic post creation into the user profile. Done with chapter 7.'

    
  1. 我知道我说过我不会再提醒你了,但我只是个普通人。让我们将这些更改推送到 GitHub。
    git push origin master

    

收工

信不信由你,这就是我们在本章要写的全部代码。把你的抗议标语收起来,上面写着“我们甚至还没有查询用户的帖子!”我们停在这里的原因是 CouchDB 有一种非常有趣的列出和处理文档的方式。为了进行讨论,我们需要定义如何使用设计文档进行视图和验证。幸运的是,这正是我们将在下一章中介绍的内容!

同时,让我们快速回顾一下我们在本章中完成的工作。

总结

在本章中,我们介绍了如何创建一个用户配置文件来显示用户信息,如何优雅地处理异常并向用户显示500和404错误页面,如何在 CouchDB 中为帖子建模,最后,创建一个表单来为登录用户创建帖子。

正如我所说,在下一章中,我们将讨论 CouchDB 带来的一些非常酷的概念。这可能是本书中最复杂的一章,但会很有趣。