Laravel-应用开发蓝图(全)

Laravel 应用开发蓝图(全)

原文:zh.annas-archive.org/md5/036252ba943f4598902eee3d22b931a1

译者:飞龙

协议:CC BY-NC-SA 4.0

前言

《Laravel 应用程序开发蓝图》介绍了如何使用 Laravel 4 逐步开发 10 个不同的应用程序。您还将了解 Laravel 内置方法的基本和高级用法,这对您的项目将非常有用。此外,您还将学习如何使用内置方法扩展当前库并包含第三方库。

本书介绍了 Laravel PHP 框架,并打破了使用 PHP 编码会导致混乱代码的成见。它将带您了解一些清晰、实用的应用程序,帮助您充分利用 Laravel PHP 框架和 PHP 面向对象编程,同时避免混乱的代码。

您还将学习使用不同方法创建安全的 Web 应用程序,例如文件上传和处理、制作 RESTful Ajax 请求和表单处理。如果您想利用 Laravel PHP 框架的验证、文件处理和 RESTful 控制器在各种类型的项目中,那么这本书就是为您准备的。本书将讨论使用 Laravel PHP 框架快速编写安全应用程序所需的一切知识。

本书内容包括

第一章,“构建 URL 缩短网站”,提供了关于 Laravel 4 的基础概述。本章介绍了路由、迁移、模型和视图的基础知识。

第二章,“使用 Ajax 构建待办事项列表”,使用 Laravel PHP 框架和 jQuery 构建应用程序。在本章中,我们将向您展示 RESTful 控制器、RESTful 路由和验证请求类型的基础知识。

第三章,“构建图像分享网站”,涵盖了如何向项目添加第三方库,以及如何上传、调整大小、处理和显示图像。

第四章,“构建个人博客”,涵盖了如何使用 Laravel 编写一个简单的个人博客。本章涵盖了 Laravel 内置的身份验证、分页机制和命名路由功能。在本章中,我们还将详细介绍一些随 Laravel 提供的快速开发方法,例如轻松为路由创建 URL 的方法。

第五章,“构建新闻聚合网站”,主要关注扩展核心类的自定义功能的使用。还涵盖了迁移的使用以及验证、保存和检索数据的基础知识。

第六章,“创建照片库系统”,帮助我们使用 Laravel 编写一个简单的照片库系统。在本章中,我们将涵盖 Laravel 内置的文件验证、文件上传和hasMany数据库关系方法。我们还将介绍用于验证数据和上传文件的验证类。最后,我们将详细介绍 Laravel 的文件类用于处理文件。

第七章,“创建通讯系统”,涵盖了一个高级的通讯系统,将使用 Laravel 的队列和电子邮件库。本章还着重介绍了如何设置和触发排队任务,以及如何解析电子邮件模板并向订阅者发送大量电子邮件。

第八章,“构建问答 Web 应用程序”,主要关注中间表的使用原因、位置和用法。本章还涵盖了第三方身份验证系统的使用方法以及删除或重命名公共段的方法。

第九章,构建 RESTful API - 电影和演员数据库,重点介绍了使用 Laravel 编写简单的电影和演员 API 的 REST 基础知识。我们将在本章中创建一些 JSON 端点,并学习一些 Laravel 4 的技巧。此外,我们还将涵盖 RESTful 控制器、RESTful 路由以及使用迁移向数据库添加示例数据的基础知识。

第十章,构建电子商务网站,讨论了如何使用 Laravel 编写简单的电子商务应用程序。在本章中,我们将介绍 Laravel 内置的基本身份验证机制,以及数据库种子。我们还将详细介绍一些与 Laravel 4 一起使用的快速开发方法。我们还将介绍关于数据透视表的高级用法。我们的电子商务应用程序将是一个简单的书店。该应用程序将具有订单、管理和购物车功能。

您需要为本书做好准备

本书中编写的应用程序都基于 Laravel 4,因此您将需要符合 Laravel 4 标准要求列表中列出的内容,该列表将在four.laravel.com/docs#server-requirements上提供。

章节要求如下:

  • PHP 5.3.7 或更高版本

  • MCrypt PHP 扩展

  • 用 SQL 数据库存储数据

个别第三方软件包可能有额外的要求。如果在章节中使用了这些软件包,请参阅它们的要求页面。

本书适合对象

这本书非常适合刚接触 PHP 5 面向对象编程标准并希望使用 Laravel PHP 框架的开发人员。假设您已经有一些 PHP 经验,并且熟悉编写当前“老派”方法,比如不使用任何 PHP 框架。这本书也适合那些已经在使用 PHP 框架并寻找更好解决方案的人。

约定

在本书中,您将找到一些文本样式,用于区分不同类型的信息。以下是一些这些样式的示例,以及它们的含义解释。

文本中的代码单词显示如下:“我们可以通过使用include指令包含其他上下文。”

代码块设置如下:

<?php
class Todo extends Eloquent
{
  protected $table = 'todos';

}

当我们希望引起您对代码块的特定部分的注意时,相关的行或项目将以粗体显示:

  public function run()
  {
    Eloquent::unguard();
    $this->call('UsersTableSeeder');
    $this->command->info('Users table seeded!');
 **$this->call('AuthorsTableSeeder');**
 **$this->command->info('Authors table seeded!');**
  }

}

任何命令行输入或输出都以以下方式编写:

**php artisan migrate**

新术语重要单词以粗体显示。您在屏幕上看到的单词,例如菜单或对话框中的单词,会以这样的方式出现在文本中:“然后,我们检查点击了最佳答案按钮的用户是问题的提问者还是应用程序的管理员。”

注意

警告或重要说明会出现在这样的框中。

提示

提示和技巧会以这种方式出现。

第一章:构建 URL 缩短网站

在整本书中,我们将使用 Laravel PHP 框架来构建不同类型的 Web 项目。

在本章中,我们将看到如何使用 Laravel 框架的基础知识构建 URL 缩短网站。涵盖的主题包括:

  • 创建数据库并迁移我们的 URL 缩短器表

  • 创建我们的表单

  • 创建我们的链接模型

  • 将数据保存到数据库

  • 从数据库获取单个 URL 并重定向

创建数据库并迁移我们的 URL 缩短器表

迁移就像是应用程序数据库的版本控制。它们允许团队(或您自己)修改数据库模式,并提供有关当前模式状态的最新信息。要创建和迁移 URL 缩短器的数据库,请执行以下步骤:

  1. 首先,我们必须创建一个数据库,并定义到 Laravel 的连接信息。为此,我们打开 app/config 下的 database.php 文件,然后填写所需的凭据。Laravel 默认支持 MySQL、SQLite、PostgreSQL 和 SQLSRV(Microsoft SQL Server)。在本教程中,我们将使用 MySQL。

  2. 我们将不得不创建一个 MySQL 数据库。为此,打开您的 MySQL 控制台(或 phpMyAdmin),并写下以下查询:

**CREATE DATABASE urls**

  1. 上一个命令将为我们生成一个名为 urls 的新的 MySQL 数据库。成功生成数据库后,我们将定义数据库凭据。要做到这一点,打开 app/config 下的 database.php 文件。在该文件中,您将看到返回多个包含数据库定义的数组。

  2. default 键定义要使用的数据库驱动程序,每个数据库驱动程序键都保存各自的凭据。我们只需要填写我们将要使用的凭据。在我们的情况下,我确保默认键的值是 mysql。要设置连接凭据,我们将填写 mysql 键的值,其中包括我们的数据库名称、用户名和密码。在我们的情况下,由于我们有一个名为 urlsdatabase,用户名为 root,没有密码,因此 database.php 文件中的 mysql 连接设置如下:

'mysql' => array(
  'driver' => 'mysql',
  'host' => 'localhost',
  'database' => 'database',
  'username' => 'root',
  'password' => '',
  'charset' => 'utf8',
  'collation' => 'utf8_unicode_ci',
  'prefix' => '',
),

提示

您可以从您在 www.packtpub.com 的帐户中下载您购买的所有 Packt 图书的示例代码文件。如果您在其他地方购买了本书,您可以访问 www.packtpub.com/support 并注册,以便直接通过电子邮件接收文件。

  1. 现在,我们将使用 Artisan CLI 来创建迁移。Artisan 是专为 Laravel 制作的命令行界面。它提供了许多有用的命令来帮助我们开发。我们将使用以下 migrate:make 命令在 Artisan 上创建一个迁移:
**php artisan migrate:make create_links_table --table=links --create**

该命令有两部分:

  • 第一个是 migrate:make create_links_table。命令的这一部分创建一个迁移文件,文件名类似于 2013_05_01_194506_create_links_table.php。我们将进一步检查该文件。

  • 命令的第二部分是 --table=links --create

  • --table=links 选项指向数据库名称。

  • --create 选项用于在我们给定 --table=links 选项的数据库服务器上创建表。

  1. 如您所见,与 Laravel 3 不同,当您运行上一个命令时,它将同时创建迁移表和我们的迁移。您可以在 app/database/migrations 下访问迁移文件,其中包含以下代码:
<?php
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateLinksTable extends Migration {
  /**
  * Run the migrations.
  *
  * @return void
  */
  public function up()
  {
    Schema::create('links', function(Blueprint $table)
    {
      $table->increments('id');
    });
  }
  /**
  * Reverse the migrations.
  *
  * @return void
  */
  public function down()
  {
    Schema::drop('links');
  }
}
  1. 让我们检查示例迁移文件。有两个公共函数声明为 up()down()。当您执行以下 migrate 命令时,将执行 up() 函数的内容:
**php artsian migrate**

此命令将执行所有迁移并在我们的情况下创建 links 表。

注意

如果在运行迁移文件时收到 class not found 错误,请尝试运行 composer update 命令。

  1. 我们还可以回滚到上一个迁移,就像它从未执行过一样。我们可以使用以下命令完成:
**php artisan migrate:rollback**

  1. 在某些情况下,我们可能还想回滚我们创建的所有迁移。这可以通过以下命令完成:
**php artisan migrate:reset**

  1. 在开发阶段,我们可能会忘记添加/删除一些字段,甚至忘记创建一些表,我们可能希望回滚所有内容并重新迁移它们。这可以使用以下命令完成:
**php artisan migrate:refresh**

  1. 现在,让我们添加我们的字段。我们创建了两个额外的字段,称为urlhashurl字段将保存实际的 URL,而hash字段中的 URL 将被重定向到hash字段中的 URL 的缩短版本。迁移文件的最终内容如下所示:
<?php
use Illuminate\Database\Migrations\Migration;
class CreateLinksTable extends Migration {
  /**
  * Run the migrations.
  *
  * @return void
  */
  public function up()
  {
    Schema::create('links', function(Blueprint $table)
    {
      $table->increments('id');
      $table->text('url');
      $table->string('hash',400);
    });
  }
  /**
  * Reverse the migrations.
  *
  * @return void
  */
  public function down()
  {
    Schema::drop('links');
  }
}

创建我们的表单

现在让我们制作我们的第一个表单视图。

  1. 将以下代码保存为form.blade.php,放在app/views下。文件的扩展名是blade.php,因为我们将受益于 Laravel 4 内置的模板引擎Blade。在表单中可能有一些您尚不理解的方法,但不要担心。我们将在本章中涵盖有关此表单的所有内容。
<!DOCTYPE html>
<html lang="en">
  <head>
    <title>URL Shortener</title>
    <link rel="stylesheet" href="/assets/css/styles.css" />
  </head>
  <body>
    <div id="container">
      <h2>Uber-Shortener</h2>
      {{Form::open(array('url'=>'/','method'=>'post'))}}

      {{Form::text('link',Input::old('link'),array('placeholder'=>'Insert your URL here and press enter!'))}}
      {{Form::close()}}
    </div>
  </body>
</html>
  1. 现在将以下代码保存为styles.css,放在public/assets/css下:
div#container{padding-top:100px;
  text-align:center;
  width:75%;
  margin:auto;
  border-radius:4px}
div#container h2{font-family:Arial,sans-serif;
  font-size:28px;
  color:#555}
div#container h3{font-family:Arial,sans-serif;
  font-size:28px}
div#container h3.error{color:#a00}
div#container h3.success{color:#0a0}
div#container input{display:block;
  width:90%;
  float:left;
  font-size:24px;
  border-radius:5px}
div#error,div#success{border-radius:3px;
  display:block;
  width:90%;
  padding:10px}
div#error{background:#ff8080;
  border:1px solid red}
div#success{background:#80ff80;
  border:1px solid #0f0}

这段代码将生成一个看起来像以下截图的表单:

创建我们的表单

正如您所看到的,我们使用了一个 CSS 文件来整理表单,但是表单的实际部分位于View文件的底部,位于 ID 为 container 的div内部。

  1. 我们使用了 Laravel 内置的Form类来生成一个表单,并使用了Input库的old()方法。现在让我们来看看代码:
  • Form::open(): 它创建一个<form>开放标签。在第一个提供的参数中,您可以定义表单的发送方式以及将要发送到哪里。它可以是控制器的操作,直接 URL 或命名路由。

  • Form::text(): 它创建一个类型为文本的<input>标签。第一个参数是输入的名称,第二个参数是输入的值,在第三个参数给定的数组中,您可以定义<input>标签的属性和其他属性。

  • Input::old(): 它将返回表单中的旧输入,在表单返回输入后。第一个参数是提交的旧输入的名称。在我们的情况下,如果表单在提交后返回(例如,如果表单验证失败),文本字段将填充我们的旧输入,我们可以在以后的请求中重用它。

  • Form::close(): 它关闭<form>标签。

创建我们的 Link 模型

为了从 Laravel 的 ORM 类Eloquent中受益,我们需要定义一个模型。将以下代码保存为Link.php,放在app/models下:

<?php
class Link extends Eloquent {
  protected $table = 'links';
  protected $fillable = array('url','hash');
  public $timestamps = false;
}

Eloquent 模型非常容易理解。

  • 变量$table用于定义模型的表名,但这并不是强制性的。即使我们不定义这个变量,它也会将模型名称的复数形式作为数据库表名。例如,如果模型名称是 post,它将默认查找 post 的表。这样,您可以为表使用任何模型名称。

  • 受保护的$fillable变量定义了可以(批量)创建和更新的列。默认情况下,Laravel 4 会阻止使用Eloquent批量赋值所有列的值。例如,如果您有一个users表,并且您是唯一的用户/管理员,批量赋值将保护您的数据库免受其他用户的添加。

  • $timestamps变量检查模型是否应该默认尝试设置时间戳created_atupdated_at,分别在创建和更新查询时。由于我们不需要这些功能,我们将通过将值设置为false来禁用它。

我们现在需要定义这个视图,以显示我们是否可以导航到我们的虚拟主机的索引页面。您可以从routes.php中定义的控制器,或直接从routes.php中定义。由于我们的应用程序很小,直接从routes.php中定义它们应该就足够了。要定义这个,打开app文件夹下的routes.php文件,并添加以下代码:

Route::get('/', function()	
{
  return View::make('form');
});

注意

如果您已经有一个以Route::get('/', function()开头的部分,您应该用先前的代码替换该部分。

Laravel 可以监听getpostputdelete请求。由于我们的操作是一个get操作(因为我们将通过浏览器导航而不是发布),所以我们的路由类型将是get,因为我们想在根页面上显示视图,所以Route::get()方法的第一个参数将是/,我们将包装一个闭包函数作为第二个参数来定义我们想要做的事情。在我们的情况下,我们将返回app/views下放置的form.blade.php,所以我们只需输入return View::make('form')。这个方法从views文件夹返回form.blade.php视图。

注意

如果视图在子目录中,它将被称为subfolder.form

将数据保存到数据库

现在我们需要编写一个路由来监听我们的post请求。为此,我们打开app文件夹下的routes.php文件,并添加以下代码:

Route::post('/',function(){
  //We first define the Form validation rule(s)
  $rules = array(
    'link' => 'required|url'
  );
  //Then we run the form validation
  $validation = Validator::make(Input::all(),$rules);
  //If validation fails, we return to the main page with an error info
  if($validation->fails()) {
    return Redirect::to('/')
    ->withInput()
    ->withErrors($validation);
  } else {
    //Now let's check if we already have the link in our database. If so, we get the first result
    $link = Link::where('url','=',Input::get('link'))
    ->first();
    //If we have the URL saved in our database already, we provide that information back to view.
    if($link) {
      return Redirect::to('/')
      ->withInput()
      ->with('link',$link->hash);
      //Else we create a new unique URL
    } else {
      //First we create a new unique Hash
      do {
        $newHash = Str::random(6);
      } while(Link::where('hash','=',$newHash)->count() > 0);

      //Now we create a new database record
      Link::create(array('url' => Input::get('link'),'hash' => $newHash
      ));

      //And then we return the new shortened URL info to our action
      return Redirect::to('/')
      ->withInput()
      ->with('link',$newHash);
    }
  }
});

验证用户的输入

使用我们现在编写的post动作函数,我们将使用 Laravel 内置的Validation类验证用户的输入。这个类帮助我们防止无效的输入进入我们的数据库。

首先,我们定义一个$rules数组来设置每个字段的规则。在我们的情况下,我们希望链接具有有效的 URL 格式。然后我们可以使用Validator::make()方法运行表单验证,并将其赋值给$validation变量。让我们了解Validator::make()方法的参数:

  • Validator::make()方法的第一个参数接受一个输入和要验证的值的数组。在我们的情况下,整个表单只有一个名为 link 的字段,所以我们使用了Input::all()方法,该方法返回表单中的所有输入。

  • 第二个参数接受要检查的验证规则。存储的$validation变量为我们提供了一些信息。例如,我们可以检查验证是否失败或通过(使用$validation->fails()$validation->passes())。这两种方法返回布尔结果,因此我们可以轻松地检查验证是否通过或失败。此外,$validation变量包含一个messages()方法,其中包含验证失败的信息。我们可以使用$validation->messages()捕获它们。

如果表单验证失败,我们将用户重定向回我们的索引页面(return Redirect::to('/')),该页面包含 URL 缩短器表单,并将一些闪存数据返回给表单。在 Laravel 中,我们通过向重定向的页面添加withVariableName对象来实现这一点。在这里使用with是强制的,这将告诉 Laravel 我们正在返回一些额外的东西。我们可以在重定向和制作视图时都这样做。如果我们正在制作视图并向最终用户显示一些内容,那么withVariableName将是变量,我们可以直接使用$VariableName调用它们,但如果我们正在重定向到一个带有withVariableName对象的页面,VariableName将是一个闪存会话数据,我们可以使用Session类(Session::get('VariableName'))来调用它。

在我们的示例中,为了返回错误,我们使用了一个特殊的withErrors($validation)方法,而不是返回$validation->messages()。我们也可以使用那个返回,但是$errors变量总是在视图上定义的,所以我们可以直接使用我们的$validation变量作为参数与withErrors()一起使用。withInput()方法也是一个特殊的方法,它将结果返回到表单。

//If validation fails, we return to the main page with an error info
if($validation->fails()) {
  return Redirect::to('/')
  ->withInput()
  ->withErrors($validation);
}

如果用户在表单中忘记了一个字段,并且验证失败并显示带有错误消息的表单,使用withInput()方法,表单可以再次填充旧的输入。为了在 Laravel 中显示这些旧的输入,我们使用Input类的old()方法。例如,Input::old('link')将返回表单字段link的旧输入。

将消息返回给视图

为了将错误消息返回到表单中,我们可以将以下 HTML 代码添加到form.blade.php中:

@if(Session::has('errors'))
<h3 class="error">{{$errors->first('link')}}</h3>
@endif

正如您可能已经猜到的那样,Session::has('variableName')返回一个布尔值,用于检查会话中是否有变量名。然后,使用 Laravel 的Validator类的first('formFieldName')方法,我们返回表单字段的第一个错误消息。在我们的情况下,我们正在显示link表单字段的第一个错误消息。

深入控制器和处理表单

在我们的示例中,验证检查部分的else部分在表单验证成功完成时执行,包含了链接的进一步处理。在这一部分,我们将执行以下步骤:

  1. 检查链接是否已经在我们的数据库中。

  2. 如果链接已经在我们的数据库中,返回缩短后的链接。

  3. 如果链接不在我们的数据库中,为链接创建一个新的随机字符串(将在我们的 URL 中)。

  4. 在我们的数据库中使用提供的数值创建一个新的记录。

  5. 将缩短后的链接返回给用户。

现在,让我们深入了解代码。

  1. 以下是我们代码的第一部分:
// Now let's check if we already have the link in our database. If so, we get the first result
$link = Link::where('url','=',Input::get('link'))
->first();

首先,我们使用Fluent Query Builderwhere()方法检查 URL 是否已经存在于我们的数据库中,并通过first()方法获取第一个结果,并将其赋给$link变量。您可以轻松地使用 Fluent 查询方法和 Eloquent ORM。如果这让您感到困惑,不用担心,我们将在后面的章节中进一步介绍。

  1. 这是我们控制器方法代码的下一部分:
//If we have the URL saved in our database already, we provide that information back to view.
if($link) {
  return Redirect::to('/')
  ->withInput()
  ->with('link',$link->hash);

如果我们在数据库中保存了 URL,$link变量将保存从数据库中获取的链接信息的对象。因此,通过简单的if()子句,我们可以检查是否有结果。如果有结果返回,我们可以使用$link->columnname来访问它。

在我们的情况下,如果查询有结果,我们将输入和链接重定向回表单。正如我们在这里使用的,with()方法也可以用两个参数而不是使用驼峰命名法——withName('value')with('name','value')完全相同。因此,我们可以使用闪存数据名为链接with('link',$link->hash)来返回哈希码。为了显示这一点,我们可以将以下代码添加到我们的表单中:

@if(Session::has('link'))
<h3 class="success">
  {{Html::link(Session::get('link'),'Click here for your shortened URL')}}</h3>
@endif

Html类帮助我们轻松编写 HTML 代码。link()方法需要以下两个参数:

  • 第一个参数是link。如果我们直接提供一个字符串(在我们的例子中是哈希字符串),该类将自动识别它并从我们的网站创建内部 URL。

  • 第二个参数是包含链接的字符串。

可选的第三个参数必须是一个数组,包含属性(例如 class、ID 和 target)作为二维数组。

  1. 以下是我们代码的下一部分:
//Else we create a new unique URL
} else {
  //First we create a new unique Hash
  do {
    $newHash = Str::random(6);
  } while(Link::where('hash','=',$newHash)->count() > 0);

如果没有结果(变量的 else 子句),我们将使用Str类的random()方法创建一个六个字符长的字母数字随机字符串,并使用 PHP 自己的 do-while 语句每次检查它是否是唯一的字符串。对于真实世界的应用,您可以使用另一种方法来缩短,例如将 ID 列中的条目转换为 base_62 并将其用作哈希值。这样,URL 将更清晰,这总是一个更好的做法。

  1. 这是我们代码的下一部分:
//Now we create a new database record
Link::create(array(
  'url' => Input::get('link'),
  'hash' => $newHash
));

一旦我们有了唯一的哈希,我们可以使用 Laravel 的 Eloquent ORM 的create()方法将链接和哈希值添加到数据库中。唯一的参数应该是一个二维数组,其中数组的键保存数据库列名,数组的值保存要插入为新行的值。

在我们的情况下,url列必须具有来自表单的link字段的值。我们可以使用 Laravel 的Input类的get()方法捕获来自post请求的这些值。在我们的情况下,我们可以使用Input::get('link')捕获来自post请求的link表单字段的值(我们可以使用$_POST['link']的混乱代码捕获),并像之前一样将哈希值返回给视图。

  1. 这是我们代码的最后部分:
//And then we return the new shortened URL info to our action return Redirect::to('/')
->withInput()
->with('link',$newHash);

现在,在输出中,我们被重定向到oursite.dev/hashcode。变量$newHash中存储了一个链接;我们需要捕获这个哈希码并查询我们的数据库,如果有记录,我们需要重定向到实际的 URL。

从数据库中获取单个 URL 并重定向

现在,在我们第一章的最后部分,我们需要从生成的 URL 中获取hash部分,如果有值,我们需要将其重定向到存储在我们数据库中的 URL。为此,请在app文件夹下的routes.php文件末尾添加以下代码:

Route::get('{hash}',function($hash) {
  //First we check if the hash is from a URL from our database
  $link = Link::where('hash','=',$hash)
  ->first();
  //If found, we redirect to the URL
  if($link) {
    return Redirect::to($link->url);
    //If not found, we redirect to index page with error message
  } else {
    return Redirect::to('/')
    ->with('message','Invalid Link');
  }
})->where('hash', '[0-9a-zA-Z]{6}');

在前面的代码中,与其他路由定义不同,我们在名称hash周围添加了花括号,告诉 Laravel 它是一个参数;并且使用where()方法定义了名称参数的方式。第一个参数是变量的名称(在我们的情况下是hash),第二个参数是一个正则表达式,用于过滤参数。在我们的情况下,正则表达式过滤了一个精确的六个字符长的字母数字字符串。这样,我们可以过滤我们的 URL 并从一开始就保护它们,而且我们不必检查url参数是否有我们不想要的内容(例如,在 ID 列中输入字母而不是数字)。要从数据库中获取单个 URL 并重定向,我们执行以下步骤:

  1. Route类中,我们首先进行搜索查询,就像在前面的部分中所做的那样,然后检查我们的数据库中是否有一个具有给定哈希的链接,并将其设置为名为$link的变量。
//First we check if the hash is from an URL from our database
$link = Link::where('hash','=',$hash)
->first();
  1. 如果有结果,我们将页面重定向到我们数据库的url列,该列包含用户应重定向到的链接。
//If found, we redirect to the link
if($link) {
  return Redirect::to($link->url);
}
  1. 如果没有结果,我们将使用$message变量将用户重定向回我们的索引页面,该变量保存了Invalid Link的值。
//If not found, we redirect to index page with error message
} else {
  return Redirect::to('/')
  ->with('message','Invalid Link');
}

要在表单中显示Invalid Link消息,请在app/views下的form.blade.php文件中添加以下代码。

@if(Session::has('message'))
<h3 class="error">{{Session::get('message')}}</h3>
@endif

总结

在本章中,我们通过制作一个简单的 URL 缩短网站,介绍了 Laravel 路由、模型、artisan 命令和数据库驱动的基本用法。一旦您完成了本章,您就可以使用迁移创建数据库表,使用 Laravel 表单构建器类编写简单的表单,使用Validation类验证这些表单,并使用 Fluent 查询构建器或 Eloquent ORM 处理这些表单并将新数据插入表中。在下一章中,我们将介绍这些强大功能的高级用法。

第二章:使用 Ajax 构建待办事项列表

在本章中,我们将使用 Laravel PHP 框架和 jQuery 来构建一个带有 Ajax 的待办事项列表。

在本章中,我们将向您展示RESTful 控制器RESTful 路由请求类型的基础知识。本章涵盖的主题列表如下:

  • 创建和迁移待办事项列表的数据库

  • 创建待办事项列表的模型

  • 创建模板

  • 使用 Ajax 向数据库插入数据

  • 从数据库中检索列表

  • 如何只允许 Ajax 请求

创建和迁移待办事项列表的数据库

正如您从上一章所知,迁移对于控制开发步骤非常有帮助。我们将在本章再次使用迁移。

要创建我们的第一个迁移,请输入以下命令:

**php artisan migrate:make create_todos_table --table=todos --create**

当您运行此命令时,Artisan将生成一个迁移以生成名为todos的数据库表。

现在我们应该编辑迁移文件以创建必要的数据库表列。当您用文件管理器打开app/database/中的migration文件夹时,您会看到其中的迁移文件。

让我们按照以下方式打开并编辑文件:

<?php
use Illuminate\Database\Migrations\Migration;
class CreateTodosTable extends Migration {

    /**
    * Run the migrations.
    *
    * @return void
    */
    public function up()
    {

        Schema::create('todos', function(Blueprint $table){
            $table->create();
            $table->increments("id");
            $table->string("title", 255);
            $table->enum('status', array('0', '1'))->default('0');
            $table->timestamps();
        });

    }

    /**
    * Reverse the migrations.
    *
    * @return void
    */
    public function down()
    {
        Schema::drop("todos");
    }

}

要构建一个简单的待办事项列表,我们需要五列:

  • id列将存储待办任务的 ID 编号

  • title列将存储待办任务的标题

  • status列将存储每个任务的状态

  • created_atupdated_at列将存储任务的创建和更新日期

如果您在迁移文件中写入$table->timestamps(),Laravel 的migration

类会自动创建created_atupdated_at列。正如您从第一章中所知,构建 URL 缩短网站,要应用迁移,我们应该运行以下命令:

**php artisan migrate**

运行命令后,如果您检查数据库,您会看到我们的todos表和列已经创建。现在我们需要编写我们的模型。

创建一个待办事项模型

要创建一个模型,您应该用文件管理器打开app/models/目录。在该目录下创建一个名为Todo.php的文件,并编写以下代码:

<?php
class Todo extends Eloquent
{
  protected $table = 'todos';

}

让我们来看一下Todo.php文件。

如您所见,我们的Todo类扩展了 Laravel 的ORM对象关系映射器)数据库类Eloquent

protected $table = 'todos';代码告诉Eloquent关于我们模型的表名。如果我们不设置table变量,Eloquent会接受小写模型名称的复数版本作为表名。因此,从技术上讲这并不是必需的。

现在,我们的应用程序需要一个模板文件,所以让我们创建它。

创建模板

Laravel 使用一个名为Blade的模板引擎来处理静态和应用程序模板文件。Laravel 从app/views/目录调用模板文件,因此我们需要在该目录下创建我们的第一个模板。

  1. 创建一个名为index.blade.php的文件。

  2. 文件包含以下代码:

<html>
  <head>
    <title>To-do List Application</title>
    <link rel="stylesheet" href="assets/css/style.css">
    <!--[if lt IE 9]><scriptsrc="//html5shim.googlecode.com/svn/trunk/html5.js"></script><![endif]-->

  </head>
  <body>
    <div class="container">
      <section id="data_section" class="todo">
        <ul class="todo-controls">
        <li><img src="/assets/img/add.png" width="14px"onClick="show_form('add_task');" /></li>
        </ul>
          <ul id="task_list" class="todo-list">
          @foreach($todos as $todo)
            @if($todo->status)
              <li id="{{$todo->id}}" class="done"><a href="#" class="toggle"></a><span id="span_{{$todo->id}}">{{$todo->title}}</span> <a href="#"onClick="delete_task('{{$todo->id}}');"class="icon-delete">Delete</a> <a href="#"onClick="edit_task('{{$todo->id}}','{{$todo->title}}');"class="icon-edit">Edit</a></li>
            @else
              <li id="{{$todo->id}}"><a href="#"onClick="task_done('{{$todo->id}}');"class="toggle"></a> <span id="span_{{$todo->id}}">{{$todo->title}}</span><a href="#" onClick="delete_task('{{$todo->id}}');" class="icon-delete">Delete</a>
                <a href="#" onClick="edit_task('{{$todo->id}}','{{$todo->title}}');"class="icon-edit">Edit</a></li>
            @endif
          @endforeach
        </ul>
      </section>
      <section id="form_section">

      <form id="add_task" class="todo"
        style="display:none">
      <input id="task_title" type="text" name="title"placeholder="Enter a task name" value=""/>
      <button name="submit">Add Task</button>
      </form>

        <form id="edit_task" class="todo"style="display:none">
        <input id="edit_task_id" type="hidden" value="" />
        <input id="edit_task_title" type="text"name="title" value="" />
        <button name="submit">Edit Task</button>
      </form>

    </section>

    </div>
    <script src="http://code.jquery.com/jquery-latest.min.js"type="text/javascript"></script>
    <script src="assets/js/todo.js"type="text/javascript"></script>
  </body>
</html>

如果您是第一次编写 Blade 模板,上面的代码可能很难理解,所以我们将尝试解释一下。您会在文件中看到一个foreach循环。这个语句循环遍历我们的todo记录。

在本章中创建控制器时,我们将为您提供更多关于它的知识。

ifelse语句用于区分已完成和待办任务。我们使用ifelse语句来为任务设置样式。

我们还需要一个模板文件,用于动态向任务列表追加新记录。在app/views/文件夹下创建一个名为ajaxData.blade.php的文件。文件包含以下代码:

@foreach($todos as $todo)
  <li id="{{$todo->id}}"><a href="#" onClick="task_done('{{$todo->id}}');" class="toggle"></a> <span id="span_{{$todo>id}}">{{$todo->title}}</span> <a href="#"onClick="delete_task('{{$todo->id}}');" class="icondelete">Delete</a> <a href="#" onClick="edit_task('{{$todo>id}}','{{$todo->title}}');" class="icon-edit">Edit</a></li>
@endforeach

此外,您会在静态文件的源路径中看到/assets/目录。当您查看app/views目录时,您会发现没有名为assets的目录。Laravel 将系统文件和公共文件分开。公共可访问文件位于root目录下的public文件夹中。因此,您应该在公共文件夹下创建一个asset文件夹。

我们建议使用这些类型的有组织的文件夹来开发整洁易读的代码。最后,您会发现我们是从 jQuery 的主网站调用的。我们还建议您以这种方式在应用程序中获取最新的稳定的 jQuery。

您可以根据自己的意愿为应用程序设置样式,因此我们不会在这里检查样式代码。我们将把我们的style.css文件放在/public/assets/css/下。

为了执行 Ajax 请求,我们需要 JavaScript 编码。这段代码发布了我们的add_taskedit_task表单,并在任务完成时更新它们。让我们在/public/assets/js/中创建一个名为todo.js的 JavaScript 文件。文件包含以下代码:

function task_done(id){

  $.get("/done/"+id, function(data) {

    if(data=="OK"){

      $("#"+id).addClass("done");
    }

  });
}
function delete_task(id){

  $.get("/delete/"+id, function(data) {

    if(data=="OK"){
      var target = $("#"+id);

      target.hide('slow', function(){ target.remove(); });

    }

  });
}

function show_form(form_id){

  $("form").hide();

  $('#'+form_id).show("slow");

}
function edit_task(id,title){

  $("#edit_task_id").val(id);

  $("#edit_task_title").val(title);

  show_form('edit_task');
}
$('#add_task').submit(function(event) {

  /* stop form from submitting normally */
  event.preventDefault();

  var title = $('#task_title').val();
  if(title){
    //ajax post the form
    $.post("/add", {title: title}).done(function(data) {

      $('#add_task').hide("slow");
      $("#task_list").append(data);
    });

  }
  else{
    alert("Please give a title to task");
    }
});

$('#edit_task').submit(function() {

  /* stop form from submitting normally */
  event.preventDefault();

  var task_id = $('#edit_task_id').val();
  var title = $('#edit_task_title').val();
  var current_title = $("#span_"+task_id).text();
  var new_title = current_title.replace(current_title, title);
  if(title){
    //ajax post the form
    $.post("/update/"+task_id, {title: title}).done(function(data){
      $('#edit_task').hide("slow");
      $("#span_"+task_id).text(new_title);
    });
  }
  else{
    alert("Please give a title to task");
  }
});

让我们检查 JavaScript 文件。

使用 Ajax 将数据插入到数据库

在这个应用程序中,我们将使用Ajax POST方法将数据插入到数据库中。jQuery 是这类应用程序的最佳 JavaScript 框架。jQuery 还带有强大的选择器功能。

我们的 HTML 代码中有两个表单,所以我们需要使用 Ajax 提交它们来插入或更新数据。我们将使用 jQuery 的post()方法来实现。

我们将在/public/assets/js下提供我们的 JavaScript 文件,因此让我们在该目录下创建一个todo.js文件。首先,我们需要一个请求来添加新任务。JavaScript 代码包含以下代码:

$('#add_task').submit(function(event) {
  /* stop form from submitting normally */
  event.preventDefault();
  var title = $('#task_title').val();
  if(title){
    //ajax post the form
    $.post("/add", {title: title}).done(function(data) {
      $('#add_task').hide("slow");
      $("#task_list").append(data);
    });
  }
  else{
    alert("Please give a title to task");
  }
});

如果用户记得为任务提供标题,这段代码将把我们的add_task表单发布到服务器。如果用户忘记为任务提供标题,代码将不会发布表单。发布后,代码将隐藏表单并向任务列表附加一个新记录。同时,我们将等待响应以获取数据。

因此,我们需要第二个表单来更新任务的标题。代码将通过 Ajax 实时更新任务的标题,并更改更新记录的文本。实时编程(或现场编码)是一种编程风格,程序员/表演者/作曲家在程序运行时增加和修改程序,而无需停止或重新启动,以在运行时断言表达式、可编程控制性能、组合和实验。由于编程语言的基本功能,我们相信实时编程的技术和美学方面值得在 Web 应用程序中探索。更新表单的代码应该如下:

$('#edit_task').submit(function(event) {
  /* stop form from submitting normally */
  event.preventDefault();
  var task_id = $('#edit_task_id').val();
  var title = $('#edit_task_title').val();
  var current_title = $("#span_"+task_id).text();
  var new_title = current_title.replace(current_title, title);
  if(title){
    //ajax post the form
    $.post("/update/"+task_id, {title: title}).done(function(data){
      $('#edit_task').hide("slow");
      $("#span_"+task_id).text(new_title);
    });
  }
  else{
    alert("Please give a title to task");
  }
});

Laravel 具有 RESTful 控制器功能。这意味着您可以定义路由和控制器函数的 RESTful 基础。此外,可以为不同的请求类型(如POSTGETPUTDELETE)定义路由。

在定义路由之前,我们需要编写我们的控制器。控制器文件位于app/controllers/下;在其中创建一个名为TodoController.php的文件。控制器代码应该如下:

<?php
class TodoController extends BaseController
{
  public $restful = true;
  public function postAdd() {
    $todo = new Todo();
    $todo->title = Input::get("title");
    $todo->save();      
    $last_todo = $todo->id;
    $todos = Todo::whereId($last_todo)->get();
    return View::make("ajaxData")->with("todos", $todos);
  }
  public function postUpdate($id) {        
    $task = Todo::find($id);
    $task->title = Input::get("title");
    $task->save();
    return "OK";        
  }
}

让我们检查代码。

正如您在代码中所看到的,RESTful 函数定义了诸如postFunctiongetFunctionputFunctiondeleteFunction之类的语法。

我们有两个提交表单,所以我们需要两个 POST 函数和一个 GET 方法来从数据库中获取记录,并在foreach语句中在模板中向访问者显示它们。

让我们检查前面的代码中的postUpdate()方法:

public function postUpdate($id) {
  $task = Todo::find($id);
  $task->title = Input::get("title");
  $task->save();
  return "OK";
}

以下几点解释了前面的代码:

  • 该方法需要一个名为id的记录来更新。我们提交的路由将类似于/update/record_id

  • $task = Todo::find($id);是从数据库中查找具有给定id的记录的方法的一部分。

  • $task->title = Input::get("title");意味着获取名为title的表单元素的值,并将title列的记录更新为发布的值。

  • $task->save();应用更改并在数据库服务器上运行更新查询。

让我们检查postAdd()方法。这个方法的工作方式类似于我们的getIndex()方法。代码的第一部分在数据库服务器上创建一个新记录:

public function postAdd() {
  $todo = new Todo();
  $todo->title = Input::get("title");
  $todo->save();      
  $last_todo = $todo->id;
  $todos = Todo::whereId($last_todo)->get();
  return View::make("ajaxData")->with("todos", $todos);
}

以下几点解释了前面的代码:

  • 代码行$last_todo = $todo->id;获取了这条记录的 ID。它相当于mysql_insert_id()函数。

  • 代码行$todos = Todo::whereId($last_todo)->get();从具有id列等于$last_todo变量的todo表中获取记录。

  • 代码行View::make("ajaxData") ->with("todos", $todos);非常重要,以了解 Laravel 的视图机制:

  • 代码行View::make("ajaxData")指的是我们的模板文件。你还记得我们在/app/views/下创建的ajaxData.blade.php文件吗?代码调用了这个文件。

  • 代码行->with("todos", $todos);将最后一条记录分配给模板文件,作为名为todos的变量(第一个参数)。因此,我们可以使用foreach循环在模板文件中显示最后一条记录。

从数据库中检索列表

我们还需要一种方法来从我们的数据库服务器中获取现有数据。在我们的控制器文件中,我们需要如下所示的函数:

public function getIndex() {
  $todos = Todo::all();
  return View::make("index")
    ->with("todos", $todos);
}

让我们来看一下getIndex()方法:

  • 在代码中,$todos = Todo:all()表示从数据库中获取所有记录并将它们分配给$todos变量。

  • 在代码中,View::make("index")定义了我们的模板文件。你还记得我们在/app/views/下创建的index.blade.php文件吗?代码调用了这个文件。

  • 在代码中,->with("todos", $todos);将记录分配给模板文件。因此,我们可以使用foreach循环在模板文件中显示记录。

最后,我们将定义我们的路由。要定义路由,您应该在apps文件夹中打开routes.php文件。Laravel 有一个很好的功能,用于定义名为 RESTful 控制器的路由。您可以使用一行代码定义所有路由,如下所示:

Route::controller('/', 'TodoController');

上述代码将所有应用程序基于根的请求分配给TodoController函数。如果需要,您也可以手动定义路由,如下所示:

Route::method('path/{variable}', 'TheController@functionName');

如何仅允许 Ajax 请求

我们的应用程序甚至可以在没有 Ajax 的情况下接受所有 POST 和 GET 请求。但我们只需要允许addupdate函数的 Ajax 请求。Laravel 的Request类为您的应用程序提供了许多检查 HTTP 请求的方法。其中一个函数名为ajax()。我们可以在控制器或路由过滤器下检查请求类型。

使用路由过滤器允许请求

路由过滤器提供了一种方便的方式来限制、访问或过滤给定路由的请求。Laravel 中包含了几个过滤器,这些过滤器位于app文件夹中的filters.php文件中。我们可以在这个文件下定义我们自定义的过滤器。我们将不在本章中使用这种方法,但我们将在后续章节中研究路由过滤器。用于 Ajax 请求的路由过滤器应该如下所示的代码所示:

Route::filter('ajax_check', function()
{
  if (Request::ajax())
    {
      return true;
    }
});

将过滤器附加到路由也非常容易。检查以下代码中显示的示例路由:

Route::get('/add', array('before' => 'ajax_check', function()
{
    return 'The Request is AJAX!';
}));

在前面的示例中,我们为具有before变量的路由定义了一个路由过滤器。这意味着我们的应用首先检查请求类型,然后调用控制器函数并传递数据。

使用控制器端允许请求

我们可以在控制器下检查请求类型。我们将在本节中使用这种方法。这种方法对于基于函数的过滤非常有用。为此,我们应该按照以下代码所示更改我们的addupdate函数:

public function postAdd() {
  if(Request::ajax()){
    $todo = new Todo();
    $todo->title = Input::get("title");
    $todo->save();
    $last_todo = $todo->id;
    $todos = Todo::whereId($last_todo)->get();
    return View::make("ajaxData")->with("todos", $todos);
  }
}
public function postUpdate($id) {
  if(Request::ajax()){
    $task = Todo::find($id);
    $task->title = Input::get("title");
    $task->save();
    return "OK"; 
  }
}

总结

在本章中,我们编写了添加新任务的代码,更新了它,并列出了任务。我们还需要更新每个状态并删除任务。为此,我们需要两个名为getDone()getDelete()的函数。正如你从本章的前几节中所了解的那样,这些函数是 RESTful 的,并接受 GET 方法请求。因此,我们的函数应该如下所示的代码所示:

public function getDelete($id) {
  if(Request::ajax()){
    $todo = Todo::whereId($id)->first();
    $todo->delete();
    return "OK";
  }
}
public function getDone($id) {
  if(Request::ajax()){
    $task = Todo::find($id);
    $task->status = 1;
    $task->save();
    return "OK";
  }
}

我们还需要更新todo.js文件。最终的 JavaScript 代码应该如下所示的代码所示:

function task_done(id){
  $.get("/done/"+id, function(data) {
    if(data=="OK"){
      $("#"+id).addClass("done");
    }
  });
}
function delete_task(id){
  $.get("/delete/"+id, function(data) {
    if(data=="OK"){
      var target = $("#"+id);
      target.hide('slow', function(){ target.remove(); });
    }
  });
}
function show_form(form_id){
  $("form").hide();
  $('#'+form_id).show("slow");
}
function edit_task(id,title){
  $("#edit_task_id").val(id);
  $("#edit_task_title").val(title);
  show_form('edit_task');
}
$('#add_task').submit(function(event) {
/* stop form from submitting normally */
  event.preventDefault();
  var title = $('#task_title').val();
  if(title){
    //ajax post the form
    $.post("/add", {title: title}).done(function(data) {
      $('#add_task').hide("slow");
      $("#task_list").append(data);
    });
  }
  else{
    alert("Please give a title to task");
  }
});
$('#edit_task').submit(function(event) {
/* stop form from submitting normally */
  event.preventDefault();
  var task_id = $('#edit_task_id').val();
  var title = $('#edit_task_title').val();
  var current_title = $("#span_"+task_id).text();
  var new_title = current_title.replace(current_title, title);
  if(title){
    //ajax post the form
    $.post("/update/"+task_id, {title:title}).done(function(data) {
      $('#edit_task').hide("slow");
      $("#span_"+task_id).text(new_title);
    });
  }
  else{
    alert("Please give a title to task");
  }
});

总结

在本节中,我们试图了解如何在 Laravel 中使用 Ajax。在整个章节中,我们使用了模板化、请求过滤、路由和 RESTful 控制器的基础知识。我们还学会了如何从数据库中更新和删除数据。

在下一章中,我们将尝试检查 Laravel 的文件验证和文件处理方法。

第三章:构建一个图片分享网站

通过这一章,我们将创建一个照片分享网站。首先,我们将创建一个图像表。然后我们将介绍调整大小和分享图像的方法。

本章涵盖以下主题:

  • 创建数据库并迁移图像表

  • 创建一个照片模型

  • 设置自定义配置值

  • 安装第三方库

  • 创建一个安全的文件上传表单

  • 验证和处理表单

  • 使用用户界面显示图像

  • 列出图像

  • 从数据库和服务器中删除图像

创建数据库并迁移图像表

成功安装 Laravel 4 并从app/config/database.php中定义数据库凭据后,创建一个名为images的数据库。为此,您可以从托管提供商的面板上创建一个新的数据库,或者如果您是服务器管理员,您可以简单地运行以下 SQL 命令:

**CREATE DATABASE images**

成功为应用程序创建数据库后,我们需要创建一个photos表并将其安装到数据库中。为此,打开您的终端,导航到项目文件夹,并运行以下命令:

php artisan migrate:make create_photos_table --table=photos –create

这个命令将为我们生成一个新的 MySQL 数据库迁移,用于创建一个名为 photos 的表。

现在我们需要定义数据库表中应该有哪些部分。对于我们的示例,我认为id 列图像标题图像文件名时间戳应该足够了。因此,打开刚刚用前面的命令创建的迁移文件,并按照以下代码更改其内容:

<?php
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreatePhotosTable extends Migration {

  /**
  * Run the migrations.
  * @return void
  */
  public function up()
    {
    Schema::create('photos', function(Blueprint $table)
    {
      $table->increments('id');
      $table->string('title',400)->default('');//the column that holds the image's name
      $table->string('image',400)->default('');//the column that holds the image's filename
      $table->timestamps();
    });
  }

  /**
  * Reverse the migrations.
  * @return void
  */
  public function down()
  {
    Schema::drop('photos');
  }
}

保存文件后,运行以下命令执行迁移:

**php artsian migrate**

如果没有发生错误,您已经准备好进行项目的下一步了。

创建一个照片模型

如您所知,对于 Laravel 上的任何与数据库操作相关的事情,使用模型是最佳实践。我们将利用Eloquent ORM

将以下代码保存为app/models/目录中的images.php

<?php
class Photo extends Eloquent {

  //the variable that sets the table name
  protected $table = 'photos';

  //the variable that sets which columns can be edited
  protected $fillable = array('title','image');

  //The variable which enables or disables the Laravel'stimestamps option. Default is true. We're leaving this hereanyways
  public $timestamps = true;
}

我们使用protected $table变量设置了表名。表的哪些列的内容可以被更新/插入将由protected $fillable变量决定。最后,模型是否可以添加/更新时间戳将由public $timestamps变量的值决定。只需设置这个模型(即使什么都不设置),我们就可以轻松地使用 Eloquent ORM 的所有优势。

我们的模型已经准备好了,现在我们可以继续下一步,开始创建我们的控制器以及上传表单。但在此之前,我们缺少一件简单的事情。图像应该上传到哪里?缩略图的最大宽度和高度是多少?要设置这些配置值(将其视为原始 PHP 的常量),我们应该创建一个新的配置文件。

设置自定义配置值

使用 Laravel,设置配置值非常容易。所有config值都在一个数组中,并且将被定义为key=>value对。

现在让我们创建一个新的配置文件。将此文件保存为app/config中的image.php

<?php

/**
 * app/config/image.php
*/

return array(

  //the folder that will hold original uploaded images
  'upload_folder' => 'uploads',

  //the folder that will hold thumbnails
  'thumb_folder' => 'uploads/thumbs',

  //width of the resized thumbnail
  'thumb_width' => 320,

  //height of the resized thumbnail
  'thumb_height' => 240

);

您可以根据自己的喜好设置任何其他设置。这取决于您的想象力。您可以使用 Laravel 内置的Config库的get()方法调用设置。示例用法如下所示:

Config::get('filename.key')

在参数之间有一个点(),它将字符串分成两部分。第一部分是Config的文件名,不包括扩展名,第二部分是配置值的键名。在我们的示例中,如果我们想要确定上传文件夹的名称,我们应该按照以下代码所示进行编写:

Config::get('image.upload_folder')

前面的代码将返回任何值。在我们的示例中,它将返回public/uploads

还有一件事:我们为我们的应用程序定义了一些文件夹名称,但我们没有创建它们。对于某些服务器配置,文件夹可能会在第一次尝试上传文件时自动创建,但如果您不创建它们,很可能会导致服务器配置错误。在public文件夹中创建以下文件夹,并使其可写:

  • uploads/

  • uploads/thumbs

现在我们应该为我们的图片站点制作一个上传表单。

安装第三方库

我们应该为我们的图片站点制作一个上传表单,然后为其创建一个控制器。但在这之前,我们将安装一个用于图像处理的第三方库,因为我们将从中受益。Laravel 4 使用Composer,因此安装包、更新包甚至更新 Laravel 都非常容易。对于我们的项目,我们将使用一个名为Intervention的库。必须按照以下步骤来安装该库:

  1. 首先,确保您通过在终端中运行php composer.phar self-update来拥有最新的composer.phar文件。

  2. 然后打开composer.json,并在require部分添加一个新值。我们库的值是intervention/image: "dev-master"

目前,我们的composer.json文件的require部分如下所示:

"require": {
  "laravel/framework": "4.0.*",
  "intervention/image": "dev-master"
}

您可以在www.packagist.org上找到更多 Composer 包。

  1. 设置完值后,打开您的终端,导航到项目的root文件夹,并输入以下命令:
**php composer.phar update**

这个命令将检查composer.json并更新所有依赖项(包括 Laravel 本身),如果添加了新的要求,它将下载并安装它们。

  1. 成功下载库后,我们现在将激活它。为此,我们参考Intervention类的网站。现在打开你的app/config/app.php,并将以下值添加到providers键中:
Intervention\Image\ImageServiceProvider
  1. 现在,我们需要设置一个别名,以便我们可以轻松调用该类。为此,在同一文件的别名键中添加以下值:
'Image' => 'Intervention\Image\Facades\Image',
  1. 该类有一个相当容易理解的注释。要调整图像大小,运行以下代码就足够了:
Image::make(Input::file('photo')->getRealPath())->resize(300, 200)->save('foo.jpg');

注意

有关Intervention类的更多信息,请访问以下网址:

intervention.olivervogel.net

现在,所有关于视图和表单处理的准备工作都已经完成;我们可以继续进行项目的下一步。

创建一个安全的文件上传表单

现在我们应该为我们的图片站点制作一个上传表单。我们必须制作一个视图文件,它将通过控制器加载。

  1. 首先,打开app/routes.php,删除以 Laravel 开头的Route::get()行,并添加以下行:
//This is for the get event of the index page
Route::get('/',array('as'=>'index_page','uses'=>'ImageController@getIndex'));
//This is for the post event of the index.page
Route::post('/',array('as'=>'index_page_post','before' =>'csrf', 'uses'=>'ImageController@postIndex'));

'as'定义了路由的名称(类似于快捷方式)。因此,如果您为路由创建链接,即使路由的 URL 发生变化,您的应用链接也不会断开。before键定义了在动作开始之前将使用哪些过滤器。您可以定义自己的过滤器,或者使用内置的过滤器。我们设置了csrf,因此在动作开始之前将进行CSRF(跨站点请求伪造)检查。这样,您可以防止攻击者向您的应用程序注入未经授权的请求。您可以使用分隔符与多个过滤器;例如,filter1|filter2

注意

您还可以直接从控制器定义 CSRF 保护。

  1. 现在,让我们为控制器创建我们的第一个方法。添加一个新文件,其中包含以下代码,并将其命名为ImageController.php,放在app/controllers/中:
<?php

class ImageController extends BaseController {

  public function getIndex()
  {
    //Let's load the form view
    return View::make('tpl.index');
  }

}

我们的控制器是 RESTful 的;这就是为什么我们的方法 index 被命名为getIndex()。在这个方法中,我们只是加载一个视图。

  1. 现在让我们使用以下代码为视图创建一个主页面。将此文件保存为frontend_master.blade.php,放在app/views/中:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" 
"http://www.w3.org/TR/html4/loose.dtd">

<html lang="en">
  <head>
  <meta http-equiv="content-type"content="text/html; charset=utf-8">
  <title>Laravel Image Sharing</title>
  {{HTML::style('css/styles.css')}}
  </head>

  <body>
    {{--Your title of the image (and yeah, blade enginehas its own commenting, cool, isn't it?)--}}
    <h2>Your Awesome Image Sharing Website</h2>

    {{--If there is an error flashdata in session(from form validation), we show the first one--}}
    @if(Session::has('errors'))
      <h3 class="error">{{$errors->first()}}</h3>
    @endif

    {{--If there is an error flashdata in session whichis set manually, we will show it--}}
    @if(Session::has('error'))
      <h3 class="error">{{Session::get('error')}}</h3>
    @endif

    {{--If we have a success message to show, we printit--}}
    @if(Session::has('success'))
      <h3 class="error">{{Session::get('success')}}</h3>
    @endif

    {{--We yield (get the contents of) the section named'content' from the view files--}}
    @yield('content')

  </body>
</html>

要添加一个CSS文件(我们将在下一步中创建),我们使用HTML类的style()方法。我们的主页面产生一个名为content的部分,它将用view files部分填充。

  1. 现在,让我们使用以下代码创建我们的view file部分。将此文件保存为index.blade.php,放在app/views/tpl/目录中:
@extends('frontend_master')

@section('content')
  {{Form::open(array('url' => '/', 'files' => true))}}
  {{Form::text('title','',array('placeholder'=>'Please insert your title here'))}}
  {{Form::file('image')}}
  {{Form::submit('save!',array('name'=>'send'))}}
  {{Form::close()}}
@stop

在上述代码的第一行中,我们告诉 Blade 引擎,我们将使用frontend_master.blade.php作为布局。这是使用 Laravel 4 中的@extends()方法完成的。

注意

如果您来自 Laravel 3,@layout已更名为@extends

借助 Laravel 的Form类,我们生成了一个带有title字段和upload字段的上传表单。与 Laravel 3 不同,要创建一个新的上传表单,我们不再使用Form::open_for_files()。它已与open()方法合并,该方法接受一个字符串或一个数组,如果要传递多个参数,则可以使用数组。我们将传递动作 URL 以及告诉它这是一个上传表单,因此我们传递了两个参数。url键用于定义表单将被提交的位置。files参数是布尔值,如果设置为true,它将使表单成为上传表单,因此我们可以处理文件。

为了保护表单并防止不必要的表单提交尝试,我们需要在表单中添加一个 CSRF 密钥hidden。多亏了 Laravel 的Form类,它会在表单打开标签后自动生成。您可以通过查看生成的表单的源代码来检查它。

自动生成的隐藏 CSRF 表单元素如下所示:

<input name="_token" type="hidden" value="SnRocsQQlOnqEDH45ewP2GLxPFUy5eH4RyLzeKm3">
  1. 现在让我们稍微整理一下表单。这与我们的项目没有直接关系,只是为了外观。将styles.css文件保存在public/css/(我们在主页面上定义的路径)中:
/*Body adjustment*/
body{width:60%; margin:auto; background:#dedede}
/*The title*/
h2{font-size:40px; text-align:center; font-family:Tahoma,Arial,sans-serif}
/*Sub title (success and error messages)*/
h3{font-size:25px; border-radius:4px; font-family:Tahoma,Arial,sans-serif; text-align:center;width:100%}
h3.error{border:3px solid #d00; background-color:#f66; color:#d00 }
h3.success{border:3px solid #0d0; background-color:#0f0; color:#0d0}p{font-size:25px; font-weight: bold; color: black;font-family: Tahoma,Arial,sans-serif}ul{float:left;width:100%;list-style:none}li{float:left;margin-right:10px}
/*For the input files of the form*/
input{float:left; width:100%; border-radius:13px;font-size:20px; height:30px; border:10px 0 10px 0;margin-bottom:20px}

我们通过将其宽度设置为 60%,使其居中对齐,并给它一个灰色的背景来样式化主体。我们还使用successerror类以及forms格式化了h2h3消息。

在样式化之后,表单将如下截图所示:

创建安全的文件上传表单

现在我们的表单已经准备好了,我们准备进入项目的下一步。

验证和处理表单

在本节中,我们将验证提交的表单,并确保必填字段存在,并且提交的文件是一张图片。然后我们将上传图片到我们的服务器,处理图片,创建缩略图,并将图片信息保存到数据库中,如下所示:

  1. 首先,我们需要定义表单验证规则。我们更喜欢将这些值添加到相关模型中,这样规则就可以重复使用,这可以防止代码变得臃肿。为此,请在app/models/目录中的photo.php文件中添加以下代码(在本章前面生成的模型中)类定义的最后一个右花括号(})之前:
//rules of the image upload form
public static $upload_rules = array(
  'title'=> 'required|min:3',
  'image'=> 'required|image'
);

我们将变量设置为public,这样它就可以在模型文件之外使用,并将其设置为静态,这样我们就可以直接访问变量。

我们希望titleimage都是必填的,而title应至少包含三个字符。此外,我们希望检查image列的 MIME 类型,并确保它是一张图片。

注意

Laravel 的 MIME 类型检查需要安装Fileinfo扩展。因此,请确保在您的 PHP 配置中启用它。

  1. 现在我们需要控制器的post方法来处理表单。在app/controllers/中的ImageController.php文件中添加此方法,放在最后一个右花括号(})之前:
public function postIndex()
{

  //Let's validate the form first with the rules which areset at the model
  $validation = Validator::make(Input::all(),Photo::$upload_rules);

  //If the validation fails, we redirect the user to theindex page, with the error messages 
  if($validation->fails()) {
    return Redirect::to('/')->withInput()->withErrors($validation);
  }
  else {

    //If the validation passes, we upload the image to thedatabase and process it
    $image = Input::file('image');

    //This is the original uploaded client name of theimage
    $filename = $image->getClientOriginalName();
    //Because Symfony API does not provide filename//without extension, we will be using raw PHP here
    $filename = pathinfo($filename, PATHINFO_FILENAME);

    //We should salt and make an url-friendly version of//the filename
    //(In ideal application, you should check the filename//to be unique)
    $fullname = Str::slug(Str::random(8).$filename).'.'.$image->getClientOriginalExtension();

    //We upload the image first to the upload folder, thenget make a thumbnail from the uploaded image
    $upload = $image->move(Config::get( 'image.upload_folder'),$fullname);

    //Our model that we've created is named Photo, thislibrary has an alias named Image, don't mix them two!
    //These parameters are related to the image processingclass that we've included, not really related toLaravel
    Image::make(Config::get( 'image.upload_folder').'/'.$fullname)->resize(Config::get( 'image.thumb_width'),null, true)->save(Config::get( 'image.thumb_folder').'/'.$fullname);

    //If the file is now uploaded, we show an error messageto the user, else we add a new column to the databaseand show the success message
    if($upload) {

      //image is now uploaded, we first need to add columnto the database
      $insert_id = DB::table('photos')->insertGetId(
        array(
          'title' => Input::get('title'),
          'image' => $fullname
        )
      );

      //Now we redirect to the image's permalink
      return Redirect::to(URL::to('snatch/'.$insert_id))->with('success','Your image is uploadedsuccessfully!');
    } else {
      //image cannot be uploaded
      return Redirect::to('/')->withInput()->with('error','Sorry, the image could not beuploaded, please try again later');
    }
  }
}

让我们逐行查看代码。

  1. 首先,我们进行了表单验证,并从我们通过Photo::$upload_rules生成的模型中调用了我们的验证规则。

  2. 然后,我们对文件名进行了加盐处理(添加额外的随机字符以增强安全性),并使文件名适合 URL。首先,我们使用 getClientOriginalName()方法获取上传的文件名,然后使用 getClientOriginalExtension()方法获取扩展名。我们使用 STR 类的 random()方法获得一个八个字符长的随机字符串对文件名进行了加盐处理。最后,我们使用 Laravel 的内置 slug()方法使文件名适合 URL。

  3. 在所有变量准备就绪后,我们首先使用 move()方法将文件上传到服务器,该方法需要两个参数。第一个参数是文件将要传输到的路径,第二个参数是上传文件的文件名。

  4. 上传后,我们为上传的图像创建了一个静态缩略图。为此,我们利用了之前实现的图像处理类 Intervention。

  5. 最后,如果一切顺利,我们将标题和图像文件名添加到数据库,并使用 Fluent Query Builder 的 insertGetId()方法获取 ID,该方法首先插入行,然后返回列的 insert_id。我们还可以通过将 create()方法设置为变量并获取 id_column 名称,如$create->id,使用 Eloquent ORM 创建行。

  6. 在一切都正常并且我们获得了insert_id之后,我们将用户重定向到一个新页面,该页面将显示缩略图、完整图像链接和一个论坛缩略图BBCode,我们将在接下来的部分中生成。

使用用户界面显示图像

现在,我们需要从控制器创建一个新的视图和方法来显示上传的图像的信息。可以按以下方式完成:

  1. 首先,我们需要为控制器定义一个GET路由。为此,打开app文件夹中的routes.php文件,并添加以下代码:
//This is to show the image's permalink on our website
Route::get('snatch/{id}',
  array('as'=>'get_image_information',
  'uses'=>'ImageController@getSnatch'))
  ->where('id', '[0-9]+');

我们在路由上定义了一个id变量,并使用正则表达式的where()方法首先进行了过滤。因此,我们不需要担心过滤 ID 字段,无论它是自然数还是其他。

  1. 现在,让我们创建我们的控制器方法。在app/controllers/中的ImageController.php中最后一个右花括号(})之前添加以下代码:
public function getSnatch($id) {
  //Let's try to find the image from database first
  $image = Photo::find($id);
  //If found, we load the view and pass the image info asparameter, else we redirect to main page with errormessage
  if($image) {
    return View::make('tpl.permalink')->with('image',$image);
  } else {
    return Redirect::to('/')->with('error','Image not found');
  }
}

首先,我们使用 Eloquent ORM 的find()方法查找图像。如果它返回 false,那意味着找到了一行。因此,我们可以简单地使用一个简单的if子句来检查是否有结果。如果有结果,我们将使用with()方法将找到的图像信息作为名为$image的变量加载到我们的视图中。如果没有找到值,我们将返回到索引页面并显示错误消息。

  1. 现在让我们创建包含以下代码的模板文件。将此文件保存为permalink.blade.php,放在app/views/tpl/中:
@extends('frontend_master')
@section('content')
<table cellpadding="0" cellspacing="0" border="0"width="100percent">
  <tr>
    <td width="450" valign="top">
      <p>Title: {{$image->title}}</p>
    {{HTML::image(Config::get('image.thumb_folder').'/'.$image->image)}}
    </td>
      <td valign="top">
      <p>Direct Image URL</p>
      <input onclick="this.select()" type="text"width="100percent" value="{{URL::to(Config::get('image.upload_folder').'/'$image->image)}}" />

      <p>Thumbnail Forum BBCode</p>
      <input onclick="this.select()" type="text"width="100percent" value="[url={{URL::to('snatch/'$image->id)}}][img]{{URL::to(Config::get('image.thumb_folder')'/'.$image->image)}}[/img][/url]" />

      <p>Thumbnail HTML Code</p>
      <input onclick="this.select()" type="text"width="100percent"value="{{HTML::entities(HTML::link(URL::to('snatch/'.$image->id),HTML::image(Config::get('image.thumb_folder').'/'$image->image)))}}" />
    </td>
  </tr>
</table>
@stop

现在,您应该对此模板中使用的大多数方法都很熟悉了。还有一个名为entities()的新方法,属于HTML类,实际上是原始 PHP 的htmlentities(),但带有一些预检查,并且是 Laravel 的方式。

此外,因为我们将$image变量返回到视图中(这是我们使用 Eloquent 直接获得的数据库行对象),我们可以在视图中直接使用$image->columnName

这将产生一个视图,如下图所示:

使用用户界面显示图像

  1. 我们为项目添加了永久链接功能,但是如果我们想要显示所有图像怎么办?为此,我们需要在系统中添加一个'all pages'部分。

列出图像

在本节中,我们将在系统中创建一个'all images'部分,该部分将具有页面导航(分页)系统。如下所示,需要遵循一些步骤:

  1. 首先,我们需要从我们的route.php文件中定义其 URL。为此,打开app/routes.php并添加以下行:
//This route is to show all images.
Route::get('all',array('as'=>'all_images','uses'=>'ImageController@getAll'));
  1. 现在,我们需要一个名为getAll()的方法(因为它将是一个 RESTful 控制器,所以在开头有一个get方法)来获取值并加载视图。为此,请打开app/controllers/ImageController.php,并在最后一个右花括号(})之前添加以下代码:
public function getAll(){

  //Let's first take all images with a pagination feature
  $all_images = DB::table('photos')->orderBy('id','desc')->paginate(6);

  //Then let's load the view with found data and pass thevariable to the view
  return View::make('tpl.all_images')->with('images',$all_images);
}

首先,我们使用paginate()方法从数据库中获取了所有图像,这将使我们能够轻松获取分页链接。之后,我们加载了用户的视图,并显示了带有分页的图像数据。

  1. 要正确查看这个,我们需要一个视图文件。将以下代码保存在名为all_image.blade.php的文件中,放在app/views/tpl/目录中:
@extends('frontend_master')

@section('content')

@if(count($images))
  <ul>

    @foreach($images as $each)
      <li>
        <a href="{{URL::to('snatch/'$each->id)}}">{{HTML::image(Config::get('image.thumb_folder')'/'.$each->image)}}</a>
      </li>
    @endforeach
  </ul> 
  <p>{{$images->links()}}</p>
@else
  {{--If no images are found on the database, we will showa no image found error message--}}
  <p>No images uploaded yet, {{HTML::link('/','care to upload one?')}}</p>
@endif
@stop

我们首先用我们的内容部分扩展了frontend_master.blade.php文件。至于内容部分,我们首先检查是否返回了任何行。如果是,那么我们将它们全部循环在列表项标签(<li>)中,并附上它们的永久链接。paginate类提供的links()方法将为我们创建分页。

注意

您可以从app/config/view.php切换分页模板。

如果没有返回行,那意味着还没有图像,因此我们会显示一个警告消息,并附上一个指向新上传页面的链接(在我们的情况下是首页)。

如果有人上传了不允许或不安全的工作图像,怎么办?您肯定不希望它们出现在您的网站上,对吧?因此,您的网站应该有一个图像删除功能。

从数据库和服务器中删除图像

我们希望在我们的脚本中有一个删除功能,使用该功能我们将从数据库和上传的文件夹中删除图像。使用 Laravel,这个过程非常简单。

  1. 首先,我们需要为该操作创建一个新路由。为此,请打开app/routes.php,并添加以下行:
//This route is to delete the image with given ID
Route::get('delete/{id}', array
('as'=>'delete_image','uses'=>
'ImageController@getDelete'))
->where('id', '[0-9]+');
  1. 现在,我们需要在ImageController中定义控制器方法getDelete($id)。为此,请打开app/controllers/ImageController.php,并在最后一个右花括号(})之前添加以下代码:
public function getDelete($id) {
  //Let's first find the image
  $image = Photo::find($id);

  //If there's an image, we will continue to the deletingprocess
  if($image) {
    //First, let's delete the images from FTP
    File::delete(Config::get('image.upload_folder').'/'$image->image);
    File::delete(Config::get('image.thumb_folder').'/'$image->image);

    //Now let's delete the value from database
    $image->delete();

    //Let's return to the main page with a success message
    return Redirect::to('/')->with('success','Image deleted successfully');

  } else {
    //Image not found, so we will redirect to the indexpage with an error message flash data.
    return Redirect::to('/')->with('error','No image with given ID found');
  }
}

让我们理解这段代码:

  1. 首先,我们查看我们的数据库,如果我们已经有了给定 ID 的图像,则使用 Eloquent ORM 的find()方法将其存储在名为$image的变量中。

  2. 如果$image的值不为 false,则数据库中有与图像匹配的图像。然后,我们使用 File 类的delete()方法删除文件。或者,您也可以使用原始 PHP 的 unlink()方法。

  3. 在文件从文件服务器中删除后,我们将从数据库中删除图像的信息行。为此,我们使用了 Eloquent ORM 的delete()方法。

  4. 如果一切顺利,我们应该重定向回主页,并显示成功消息,说明图像已成功删除。

注意

在实际应用中,您应该为此类操作创建一个后端界面。

总结

在本章中,我们使用 Laravel 的内置功能创建了一个简单的图像分享网站。我们学会了如何验证我们的表单,如何处理文件并检查它们的 MIME 类型,并设置自定义配置值。我们还学习了使用 Fluent 和 Eloquent ORM 的数据库方法。此外,对于图像处理,我们使用 Composer 从packagist.org安装了第三方库,并学会了如何更新它们。我们还使用页面导航功能列出了图像,并学会了如何从服务器中删除文件。在下一章中,我们将构建一个带有身份验证和仅限会员区域的个人博客网站,并将博客文章分配给作者。

第四章:构建个人博客

在本章中,我们将使用 Laravel 编写一个简单的个人博客。我们还将介绍 Laravel 内置的身份验证、分页机制和命名路由。我们将详细介绍一些快速开发方法,这些方法是 Laravel 自带的,比如创建路由 URL。本章将涵盖以下主题:

  • 创建和迁移帖子数据库

  • 创建一个帖子模型

  • 创建和迁移作者数据库

  • 创建一个仅限会员的区域

  • 保存博客帖子

  • 将博客帖子分配给用户

  • 列出文章

  • 对内容进行分页

创建和迁移帖子数据库

我们假设你已经在app/config/database.php文件中定义了数据库凭据。对于这个应用程序,我们需要一个数据库。你可以简单地创建并运行以下 SQL 命令,或者基本上你可以使用你的数据库管理界面,比如 phpMyAdmin:

**CREATE DATABASE laravel_blog**

成功创建应用程序的数据库后,首先我们需要创建一个帖子表并将其安装在数据库中。要做到这一点,打开你的终端,导航到你的项目文件夹,并运行这个命令:

**php artisan migrate:make create_posts_table --table=posts --create**

这个命令将在app/database/migrations下生成一个迁移文件,用于在我们的laravel_blog数据库中生成一个名为posts的新 MySQL 表。

为了定义我们的表列和规范,我们需要编辑这个文件。编辑迁移文件后,它应该看起来像这样:

<?php

use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreatePostsTable extends Migration {

  /**
   * Run the migrations.
   *
   * @return void
   */
  public function up()
  {
    Schema::create('posts', function(Blueprint $table)
    {
      $table->increments('id');
      $table->string('title');
      $table->text('content');
      $table->integer('author_id');
      $table->timestamps();
    });
  }

  /**
   * Reverse the migrations.
   *
   * @return void
   */
  public function down()
  {
    Schema::drop('posts');
  }
}

保存文件后,我们需要使用一个简单的artisan命令来执行迁移:

**php artisian migrate**

如果没有错误发生,请检查laravel_blog数据库中的posts表和列。

创建一个帖子模型

如你所知,对于 Laravel 上的任何与数据库操作相关的事情,使用模型是最佳实践。我们将受益于 Eloquent ORM。

将这段代码保存在一个名为Posts.php的文件中,放在app/models/下:

<?php
class Post extends Eloquent {

protected $table = 'posts';

protected $fillable = array('title','content','author_id');

public $timestamps = true;

public function Author(){

      return $this->belongsTo('User','author_id');
}

}

我们已经使用受保护的$table变量设置了数据库表名。我们还使用了$fillable变量设置可编辑的列,并使用了$timestamps变量设置时间戳,就像我们在之前的章节中已经看到并使用过的那样。在模型中定义的变量足以使用 Laravel 的 Eloquent ORM。我们将在本章的将博客帖子分配给用户部分介绍公共的Author()函数。

我们的帖子模型已经准备好了。现在我们需要一个作者模型和数据库来将博客帖子分配给作者。让我们研究一下 Laravel 内置的身份验证机制。

创建和迁移作者数据库

与大多数 PHP 框架相反,Laravel 有一个基本的身份验证类。身份验证类在快速开发应用程序方面非常有帮助。首先,我们需要一个应用程序的秘钥。应用程序的秘钥对于我们应用程序的安全非常重要,因为所有数据都是使用这个秘钥进行哈希加盐的。artisan命令可以用一个单一的命令行为我们生成这个秘钥:

**php artisian key:generate**

如果没有错误发生,你将看到一条消息,告诉你秘钥已经成功生成。在生成秘钥后,如果你在打开 Laravel 应用程序时遇到问题,只需清除浏览器缓存,然后重试。接下来,我们应该编辑身份验证类的配置文件。为了使用 Laravel 内置的身份验证类,我们需要编辑位于app/config/auth.php的配置文件。该文件包含了身份验证设施的几个选项。如果你需要更改表名等,你可以在这个文件下进行更改。默认情况下,Laravel 自带User模型。你可以看到位于app/models/下的User.php文件。在 Laravel 4 中,我们需要定义Users模型中哪些字段是可填充的。让我们编辑位于app/models/下的User.php并添加"fillable"数组:

<?php

use Illuminate\Auth\UserInterface;
use Illuminate\Auth\Reminders\RemindableInterface;

class User extends Eloquent implements UserInterface, RemindableInterface {

  /**
   * The database table used by the model.
   *
   * @var string
   */
  protected $table = 'users';

  /**
   * The attributes excluded from the model's JSON form.
   *
   * @var array
   */
  protected $hidden = array('password');

  //Add to the "fillable" array
   protected $fillable = array('email', 'password', 'name');

  /**
   * Get the unique identifier for the user.
   *
   * @return mixed
   */
  public function getAuthIdentifier()
  {
    return $this->getKey();
  }

  /**
   * Get the password for the user.
   *
   * @return string
   */
  public function getAuthPassword()
  {
    return $this->password;
  }

  /**
   * Get the e-mail address where password reminders are sent.
   *
   * @return string
   */
  public function getReminderEmail()
  {
    return $this->email;
  }

}

基本上,我们需要为我们的作者有三列。这些是:

  • email:这一列存储作者的电子邮件

  • password:这一列存储作者的密码

  • name:这一列存储作者的名字和姓氏

现在我们需要几个迁移文件来创建users表并向我们的数据库添加作者。要创建一个迁移文件,可以给出以下命令:

**php artisan migrate:make create_users_table --table=users --create**

打开最近创建的迁移文件,位于app/database/migrations/。我们需要编辑up()函数如下:

  public function up()
  {
    Schema::create('users', function(Blueprint $table)
    {
      $table->increments('id');
      $table->string('email');
      $table->string('password');
      $table->string('name');
      $table->timestamps();
    });
  }

编辑迁移文件后,运行migrate命令:

**php artisian migrate**

如你所知,该命令创建了users表及其列。如果没有错误发生,请检查laravel_blog数据库中的users表和列。

现在我们需要创建一个新的迁移文件,向数据库中添加一些作者。我们可以通过运行以下命令来实现:

**php artisan migrate:make add_some_users**

打开迁移文件并编辑up()函数如下:

  public function up()
  {
    User::create(array(
            'email' => 'your@email.com',
            'password' => Hash::make('password'),
            'name' => 'John Doe'
        ));
  }

我们在up()函数中使用了一个新的类,名为Hash。Laravel 有一个基于安全Bcrypt的哈希制造/检查类。Bcrypt 是一种被接受的、安全的哈希方法,用于重要数据,如密码。

我们在本章开头使用 artisan 工具创建应用程序密钥的类用于加盐。因此,要应用迁移,我们需要使用以下 artisan 命令进行迁移:

**php artisian migrate**

现在,检查users表是否有记录。如果你检查password列,你会看到记录存储如下:

**$2y$08$ayylAhkVNCnkfj2rITbQr.L5pd2AIfpeccdnW6.BGbA.1VtJ6Sdqy**

安全地存储用户的密码和关键数据非常重要。不要忘记,如果你更改应用程序密钥,所有现有的哈希记录将无法使用,因为Hash类在验证和存储给定数据时使用应用程序密钥作为盐键。

创建一个仅会员可访问的区域

我们的博客系统是基于会员的。因此,我们需要一些区域只能会员访问,以便添加新的博客文章。我们有两种不同的方法来实现这一点。第一种是路由过滤器方法,我们将在接下来的章节中详细介绍。第二种是基于模板的授权检查。这种方法是更有效地理解Auth类与Blade 模板系统的使用方式。

通过Auth类,我们可以通过一行代码来检查访问者的授权状态:

Auth::check();

基于Auth类的check()函数总是返回truefalse。这意味着我们可以在我们的代码中轻松地在if/else语句中使用该函数。如你从之前的章节所知,使用 blade 模板系统,我们能够在模板文件中使用这种类型的 PHP 语句。

在创建模板文件之前,我们需要编写我们的路由。我们的应用程序需要四个路由。它们是:

  • 创建一个登录路由来处理登录请求

  • 创建一个处理新文章请求的新文章路由

  • 一个用于显示新文章表单和登录表单的管理路由

  • 一个用于列出文章的索引路由

命名路由是 Laravel 框架的另一个令人惊叹的特性,用于快速开发。命名路由允许在生成重定向或 URL 时更舒适地引用路由。你可以按以下方式为路由指定名称:

Route::get('all/posts', array('as' => 'posts', function()
{
    //
}));

你也可以为控制器指定路由名称:

Route::get('all/posts', array('as' => 'allposts', , 'uses' => 'PostController@showPosts'));

由于命名路由,我们可以轻松地为我们的应用程序创建 URL:

$url = URL::route('allposts');

我们也可以使用命名路由进行重定向:

$redirect = Redirect::route('allposts');

打开路由配置文件,位于app/routes.php,并添加以下代码:

Route::get('/', array('as' => 'index', 'uses' => 'PostsController@getIndex'));
Route::get('/admin', array('as' => 'admin_area', 'uses' => 'PostsController@getAdmin'));
Route::post('/add', array('as' => 'add_new_post', 'uses' => 'PostsController@postAdd'));
Route::post('/login', array('as' => 'login', 'uses' => 'UsersController@postLogin'));
Route::get('/logout', array('as' => 'logout', 'uses' => 'UsersController@getLogout'));

现在我们需要编写应用程序的控制器端和模板的代码。首先,我们可以从我们的管理区域开始编码。让我们在app/views/下创建一个名为addpost.blade.php的文件。我们的管理模板应该如下所示:

<html>
<head>
<title>Welcome to Your Blog</title>
<link rel="stylesheet" type="text/css" href="/assets/css/style.css">
<!--[if lt IE 9]><script src="//html5shim.googlecode.com/svn/trunk/html5.js"></script><![endif]-->
</head>
<body>
@if(Auth::check())
<section class="container">
<div class="content">
<h1>Welcome to Admin Area, {{Auth::user()->name}} ! - <b>{{link_to_route('logout','Logout')}}</b></h1>
<form name="add_post" method="POST" action="{{URL::route('add_new_post')}}">
<p><input type="text" name="title" placeholder="Post Title" value=""/></p>
<p><textarea name="content" placeholder="Post Content"></textarea></p>
<p><input type="submit" name="submit" /></p>
</div>
</section>
@else
<section class="container">
<div class="login">
<h1>Please Login</h1>
<form name="login" method="POST" action="{{URL::route('login')}}">
<p><input type="text" name="email" value="" placeholder="Email"></p>
<p><input type="password" name="password" value="" placeholder="Password"></p>
<p class="submit"><input type="submit" name="commit" value="Login"></p>
</form>
</div>
</section>
@endif
</body>
</html>

正如你在代码中所看到的,我们在模板中使用if/else语句来检查用户的登录凭据。我们从本节的开头就已经知道,我们使用Auth::check()函数来检查用户的登录状态。此外,我们还使用了一种新的方法来获取当前登录用户的名称:

Auth::user()->name;

我们可以使用user方法获取关于当前用户的任何信息:

Auth::user()->id; 
Auth::user()->email;

模板代码首先检查访问者的登录状态。如果访问者已登录,则模板显示一个新文章表单;否则显示一个登录表单。

现在我们需要编写博客应用程序的控制器端。让我们从我们的用户控制器开始。在 app/controller/ 下创建一个名为 UsersContoller.php 的文件。控制器的最终代码应如下所示:

<?php

class UsersController extends BaseController{

  public function postLogin()
  {
    Auth::attempt(array('email' => Input::get('email'),'password' => Input::get('password')));
  return Redirect::route('add_new_post');

  }

  public function getLogout()
  {
    Auth::logout();
    return Redirect::route('index');
  }
}

控制器有两个函数:第一个是 postLogin() 函数。该函数基本上检查用户登录的表单数据,然后将访问者重定向到 add_new_post 路由以显示新文章表单。第二个函数处理注销请求,并重定向到 index 路由。

保存博客文章

现在我们需要为我们的博客文章创建一个控制器。因此,在 app/controller/ 下创建一个名为 PostsContoller.php 的文件。控制器的最终代码应如下所示:

<?php
class PostsController extends BaseController{

  public function getIndex()
  {

  $posts = Post::with('Author')-> orderBy('id', 'DESC')->get();
  return View::make('index')->with('posts',$posts);

  }
  public function getAdmin()
  {
  return View::make('addpost');
  }
  public function postAdd()
  {
  Post::create(array(
              'title' => Input::get('title'),
              'content' => Input::get('content'),
              'author_id' => Auth::user()->id
   ));
  return Redirect::route('index');
  }
}

将博客文章分配给用户

postAdd() 函数处理数据库上的新博客文章创建请求。正如您所看到的,我们可以使用先前提到的方法获取作者的 ID:

Auth::user()->id

使用这种方法,我们可以为当前用户分配一个博客文章。正如您将看到的,我们在查询中有一个新方法:

Post::with('Author')->

如果您记得,我们在我们的 Posts 模型中定义了一个公共的 Author() 函数:

public function Author(){

      return $this->belongsTo('User','author_id');
}

belongsTo() 方法是一个 Eloquent 函数,用于创建表之间的关系。基本上,该函数需要一个必需的变量和一个可选的变量。第一个变量(必需)定义了目标 Model。第二个可选变量用于定义当前模型表的源列。如果不定义可选变量,Eloquent 类会搜索 targetModelName_id 列。在 posts 表中,我们将作者的 ID 存储在 author_id 列中,而不是在名为 user_id 的列中。因此,我们需要在函数中定义第二个可选变量。使用这种方法,我们可以将博客文章及其所有作者的信息传递到模板文件中。您可以将该方法视为某种 SQL 连接方法。

当我们想在查询中使用这些关系函数时,我们可以轻松地调用它们如下所示:

Books::with('Categories')->with('Author')->get();

使用较少的变量管理模板文件很容易。现在我们只需要一个变量来传递模板文件,其中包含所有必要的数据。因此,我们需要第二个模板文件来列出我们的博客文章。这个模板将在我们博客的前端工作。

列出文章

在本章的前几节中,我们已经学会了在 blade 模板文件中使用 PHP if/else 语句。Laravel 将数据作为数组传递到模板文件中。因此,我们需要使用 foreach 循环将数据解析到模板文件中。我们还可以在模板文件中使用 foreach 循环。因此,在 app/views/ 下创建一个名为 index.blade.php 的文件。代码应如下所示:

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>My Awesome Blog</title>
<link rel="stylesheet" href="/assets/blog/css/styles.css" type="text/css" media="screen" />
<link rel="stylesheet" type="text/css" href="/assets/blog/css/print.css" media="print" />
<!--[if IE]><script src="http://html5shiv.googlecode.com/svn/trunk/html5.js"></script><![endif]-->
</head>
<body>
<div id="wrapper">
<header>
<h1><a href="/">My Awesome Blog</a></h1>
<p>Welcome to my awesome blog</p>
</header>
<section id="main">
<section id="content">
@foreach($posts as $post)
<article>
<h2>{{$post->title}}</h2>
<p>{{$post->content}}</p>
<p><small>Posted by <b>{{$post->Author->name}}</b> at <b>{{$post->created_at}}</b></small></p>
</article>

@endforeach          
</section>
</aside>
</section>
<footer>
<section id="footer-area">
<section id="footer-outer-block">
<aside class="footer-segment">
<h4>My Awesome Blog</h4>
</aside>
</section>
</section>
</footer>
</div>
</body>
</html>

让我们来看看代码。我们在模板文件中使用了 foreach 循环来解析所有博客文章数据。此外,我们还在 foreach 循环中看到了组合作者数据的使用。正如您可能记得的那样,我们在模型端使用 belongsTo() 方法获取作者信息。整个关系数据解析都在一个名为关系函数名称的数组中完成。例如,如果我们有一个名为 Categories() 的第二个关系函数,那么在控制器端查询将如下所示:

$books = Books::with('Author')-> with('Categories')->orderBy('id', 'DESC')->get();

foreach 循环如下所示:

@foreach($books as $book)

<article>
<h2>{{$book->title}}</h2>
<p>Author: <b>{{$book->Author->name}}</b></p>
<p>Category: <b>{{$book->Category->name}}</b></p>
</article>

@endforeach

对内容进行分页

Eloquentget() 方法在控制器端的 Eloquent 查询中使用,从数据库中获取所有数据。通常,我们需要对内容进行分页,以便用户友好的前端或减少页面加载和优化。Eloquent 类有一个快速执行此操作的有用方法,称为 paginate()。该方法获取分页数据并在模板中生成分页链接,只需一行代码。打开 app/controllers/PostsController.php 文件,并将查询更改为如下所示:

$posts = Post::with('Author')->orderBy('id', 'DESC')->paginate(5);

paginate() 方法使用给定的数字值对数据进行分页。因此,博客文章将每页分页为 5 篇博客文章。我们还需要更改我们的模板以显示分页链接。打开 app/views/index.blade.php,并在 foreach 循环之后添加以下代码:

{{$posts->links()}}

模板中具有 ID 为 "main" 的部分应如下所示:

<section id="main">
<section id="content">
@foreach($posts as $post)

<article>
<h2>{{$post->title}}</h2>
<p>{{$post->content}}</p>
<p><small>Posted by <b>{{$post->Author->name}}</b> at <b>{{$post->created_at}}</b></small></p>
</article>
@endforeach

</section>
{{$posts->links()}}
</section>

links() 函数将自动生成分页链接,如果有足够的数据进行分页。否则,该函数不显示任何内容。

摘要

在本章中,我们使用 Laravel 的内置函数和 Eloquent 数据库驱动程序创建了一个简单的博客。我们学习了如何对数据进行分页以及 Eloquent 的基本数据关系机制。同时,我们也介绍了 Laravel 的内置身份验证机制。在接下来的章节中,我们将学习如何处理更复杂的表格和关联数据。

第五章:构建新闻聚合网站

在本章中,我们将创建一个新闻聚合网站。我们将解析多个源,对它们进行分类,为我们的网站激活/停用它们,并使用 PHP 的 SimpleXML 扩展在我们的网站上显示它们。本章将涵盖以下主题:

  • 创建数据库并迁移 feeds 表

  • 创建 feeds 模型

  • 创建我们的表单

  • 验证和处理表单

  • 扩展核心类

  • 读取和解析外部源

创建数据库并迁移 feeds 表

成功安装 Laravel 4 并从app/config/database.php定义数据库凭据后,创建一个名为feeds的数据库。

创建数据库后,打开终端,进入项目文件夹,并运行此命令:

**php artisan migrate:make create_feeds_table --table=feeds --create**

这个命令将为我们生成一个名为feeds的新数据库迁移。现在导航到app/database/migrations,打开刚刚由前面的命令创建的迁移文件,并将其内容更改如下:

<?php
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreateFeedsTable extends Migration {

  /**
   * Run the migrations.
   *
   * @return void
   */
  public function up()
  {
    Schema::create('feeds', function(Blueprint $table)
    {
      $table->increments('id');
      $table->enum('active', array('0', '1'));
      $table->string('title,100)->default('');
      $table->enum('category', array('News', 'Sports','Technology'));
      $table->string('feed',1000)->default('');
      $table->timestamps();
    });
  }

  /**
   * Reverse the migrations.
   *
   * @return void
   */
  public function down()
  {
    Schema::drop('feeds');
  }

}

我们有一个title列用于在网站上显示标题,这更加用户友好。此外,我们设置了一个名为active的键,因为我们想要启用/禁用源;我们使用 Laravel 4 提供的新enum()方法进行设置。我们还设置了一个category列,也使用enum()方法进行分组源的设置。

保存文件后,运行以下命令执行迁移:

**php artisan migrate**

如果没有错误发生,您已经准备好进行项目的下一步了。

创建 feeds 模型

如您所知,对于 Laravel 上的任何与数据库操作相关的事情,使用模型是最佳实践。我们将受益于 Eloquent ORM。

将此文件保存为feeds.php,放在app/models/下:

<?php
Class Feeds Extends Eloquent{
    protected $table = 'feeds';
    protected $fillable = array('feed', 'title', 'active','category');
}

我们设置表名和可填充列的值。现在我们的模型已经准备好,我们可以继续下一步,开始创建我们的控制器和表单。

创建我们的表单

现在我们应该创建一个表单来保存记录到数据库并指定其属性。

  1. 首先,打开终端并输入以下命令:
**php artisan controller:make FeedsController**

这个命令将为您在app/controllers文件夹中生成一个FeedsController.php文件,并带有一些空白方法。

注意

artisan命令自动填充的控制器中的默认方法不是 RESTful 的。

  1. 现在,打开app/routes.php并添加以下行:
**//We defined a RESTful controller and all its via route directly**
**Route::controller('feeds', 'FeedsController');**

我们可以使用一行代码定义控制器上声明的所有操作,而不是逐个定义所有操作。如果您的方法名称可以直接用作 get 或 post 操作,使用controller()方法可以节省大量时间。第一个参数设置控制器的 URI,第二个参数定义controllers文件夹中将被访问和定义的类。

注意

以这种方式设置的控制器自动是 RESTful 的。

  1. 现在,让我们创建表单的方法。将以下代码添加到您的控制器文件中:
  //The method to show the form to add a new feed
  public function getCreate() {
    //We load a view directly and return it to be served
    return View::make('create_feed');
      }

这里的过程非常简单;我们将方法命名为getCreate(),因为我们希望我们的create方法是 RESTful 的。我们只是加载了一个视图文件,我们将在下一步直接生成它。

  1. 现在让我们创建我们的视图文件。将此文件保存为create_feed.blade.php,放在app/views/下:
<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Save a new ATOM Feed to Database</title>
</head>
<body>
  <h1>Save a new ATOM Feed to Database</h1>
  @if(Session::has('message'))
    <h2>{{Session::get('message')}}</h2>
  @endif
    {{Form::open(array('url' => 'feeds/create', 'method' => 'post'))}}
    <h3>Feed Category</h3>
  {{Form::select('category',array('News'=>'News','Sports'=>'Sports','Technology'=>'Technology'),Input::old('category'))}}
  <h3>Title</h3>
    {{Form::text('title',Input::old('title'))}}
    <h3>Feed URL</h3>
    {{Form::text('feed',Input::old('feed'))}}

    <h3>Show on Site?</h3>
{{Form::select('active',array('1'=>'Yes','2'=>'No'),Input::old('active'))}}
    {{Form::submit('Save!',array('style'=>'margin:20px 100% 0 0'))}}
    {{Form::close()}}
</body>
</html>

上述代码将生成一个简单的表单,如下所示:

创建我们的表单

验证和处理表单

在本节中,我们将验证提交的表单,并确保字段有效且必填字段已填写。然后我们将数据保存到数据库中。

  1. 首先,我们需要定义表单验证规则。我们更喜欢将验证规则添加到相关模型中,这样规则就可以重复使用,这可以防止代码变得臃肿。为此,在本章前面生成的feeds.php中的app/models/(我们生成的模型)中,类定义的最后一个}之前添加以下代码:
//Validation rules
public static $form_rules = array(
  'feed'    => 'required|url|active_url',
  'title'  => 'required'
  'active'  => 'required|between:0,1',
  'category'  => 'required| in:News,Sports,Technology'
);

我们将变量设置为public,这样它可以在模型文件之外使用,并将其设置为static,这样我们可以直接访问这个变量。

我们希望 feed 是一个 URL,并且我们希望使用active_url验证规则来检查它是否是一个活动的 URL,这取决于 PHP 的chkdnsrr()方法。

我们的 active 字段只能获得两个值,10。由于我们将其设置为整数,我们可以使用 Laravel 的表单验证规则between来检查数字是否在10之间。

我们的 category 字段也具有enum类型,其值应该只是NewsSportsTechnology。要使用 Laravel 检查确切的值,你可以使用验证规则in

注意

并非所有的服务器配置都支持chkdnsrr()方法,所以确保它在你这边已安装,否则你可能只依赖于验证 URL 是否格式正确。

  1. 现在我们需要一个控制器的 post 方法来处理表单。在最后一个}之前,将以下方法添加到app/controllers/FeedsController.php中:
//Processing the form
public function postCreate(){

//Let's first run the validation with all provided input
  $validation = Validator::make(Input::all(),Feeds::$form_rules);
  //If the validation passes, we add the values to the database and return to the form 
  if($validation->passes()) {
    //We try to insert a new row with Eloquent
    $create = Feeds::create(array(
      'feed'    => Input::get('feed'),
      'title'  => Input::get('title'),
      'active'  => Input::get('active'),
      'category'  => Input::get('category')
    ));

    //We return to the form with success or error message due to state of the 
    if($create) {
      return Redirect::to('feeds/create')
        ->with('message','The feed added to the database successfully!');
    } else {
      return Redirect::to('feeds/create')
        ->withInput()
        ->with('message','The feed could not be added, please try again later!');
    }
  } else {
    //If the validation does not pass, we return to the form with first error message as flash data
    return Redirect::to('feeds/create')
        ->withInput()
        ->with('message',$validation->errors()->first());

  }
}

让我们逐一深入代码。首先,我们进行了表单验证,并从我们通过Feeds::$form_rules生成的模型中调用了我们的验证规则。

之后,我们创建了一个if()语句,并用它将代码分成两部分。如果表单验证失败,我们将使用withInput()特殊方法返回到表单,并使用with()方法添加一个 flash 数据消息字段。

如果表单验证通过,我们尝试使用 Eloquent 的create()方法向数据库添加新列,并根据create方法返回的结果返回到表单,显示成功或错误消息。

现在,我们需要为索引页面创建一个新的视图,它将显示所有 feed 的最后五个条目。但在此之前,我们需要一个函数来解析 Atom feeds。为此,我们将扩展 Laravel 的内置Str类。

扩展核心类

Laravel 有许多内置的方法,使我们的生活更轻松。但是,就像所有捆绑包一样,捆绑包本身可能不会满足任何用户,因为它是被引入的。因此,你可能希望使用自己的方法以及捆绑的方法。你总是可以创建新的类,但是如果你想要实现的一半已经内置了呢?例如,你想添加一个表单元素,但已经有一个Form类捆绑了。在这种情况下,你可能希望扩展当前的类,而不是创建新的类来保持代码整洁。

在这一部分,我们将使用名为parse_atom()的方法来扩展Str类,我们将编写这个方法。

  1. 首先,你必须找到类文件所在的位置。我们将扩展Str类,它位于vendor/laravel/framework/src/Illuminate/Support下。请注意,你也可以在app/config/app.php的 aliases 键中找到这个类。

  2. 现在在app/folder下创建一个名为lib的新文件夹。这个文件夹将保存我们的类扩展。因为Str类被分组到Support文件夹下,建议你也在lib下创建一个名为Support的新文件夹。

  3. 现在在app/lib/Support下创建一个名为Str.php的新文件,你刚刚创建的:

<?php namespace app\lib\Support;
class Str extends \Illuminate\Support\Str {
    //Our shiny extended codes will come here
  }

我们给它命名空间,这样我们就可以轻松地访问它。你可以直接使用Str::trim(),而不是像\app\lib\Support\Str::trim()那样使用它(你可以)。其余的代码解释了如何扩展库。我们提供了从Illuminate路径开始的类名,以直接访问Str类。

  1. 现在打开位于app/config/下的app.php文件;注释掉以下行:
'Str'             => 'Illuminate\Support\Str',
  1. 现在,添加以下行:
'Str'             => 'app\lib\Support\Str',

这样,我们用我们的类替换了自动加载的Str类,而我们的类已经扩展了原始类。

  1. 现在为了在 autoruns 上进行标识,打开你的composer.json文件,并将这些行添加到 autoload 的classmap对象中:
"app/lib",
"app/lib/Support"
  1. 最后,在终端中运行以下命令:
**php composer.phar dump-autoload**

这将寻找依赖项并重新编译常见类。如果一切顺利,你现在将拥有一个扩展的Str类。

注意

文件夹和类名在 Windows 服务器上也是区分大小写的。

读取和解析外部反馈

我们在服务器上已经对反馈的 URL 和标题进行了分类。现在我们要做的就是解析它们并展示给最终用户。这需要遵循一些步骤:

  1. 首先,我们需要一个方法来解析外部 Atom 反馈。打开位于app/lib/Support/下的Str.php文件,并将此方法添加到类中:
public static function parse_feed($url) {
    //First, we get our well-formatted external feed
    $feed = simplexml_load_file($url);
    //if cannot be found, or a parse/syntax error occurs, we return a blank array
    if(!count($feed)) {
      return array();
    } else {
      //If found, we return the newest five <item>s in the <channel>
      $out = array();
      $items = $feed->channel->item;
      for($i=0;$i<5;$i++) {
        $out[] = $items[$i];
      }
      //and we return the output
      return $out;
    }
  }

首先,我们使用 SimpleXML 的内置方法simplexml_load_file()在方法中加载 XML 反馈。如果没有找到结果或者反馈包含错误,我们就返回一个空数组。在 SimpleXML 中,所有对象及其子对象都与 XML 标签完全一样。所以如果有一个<channel>标签,就会有一个名为channel的对象,如果在<channel>内有<item>标签,那么在每个channel对象下面就会有一个名为item的对象。所以如果你想访问通道内的第一项,你可以这样访问:$xml->channel->item[0]

  1. 现在我们需要一个视图来显示内容。首先打开app下的routes.php,并删除默认存在的get路由:
Route::get('/', array('as'=>'index', 'uses' =>'FeedsController@getIndex'));
  1. 现在打开FeedsController.php,位于app/controller/下,并粘贴以下代码:
public function getIndex(){
  //First we get all the records that are active category by category:
    $news_raw   = Feeds::whereActive(1)->whereCategory('News')->get();
    $sports_raw  = Feeds::whereActive(1)->whereCategory('Sports')->get();
    $technology_raw = Feeds::whereActive(1)->whereCategory('Technology')->get();

  //Now we load our view file and send variables to the view
  return View::make('index')
    ->with('news',$news_raw)
    ->with('sports',$sports_raw)
    ->with('technology',$technology_raw);
  }

在控制器中,我们逐个获取反馈的 URL,然后加载一个视图,并将它们逐个设置为每个类别的单独变量。

  1. 现在我们需要循环每个反馈类别并显示其内容。将以下代码保存在名为index.blade.php的文件中,放在app/views/下:
<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Your awesome news aggregation site</title>
    <style type="text/css">
    body { font-family: Tahoma, Arial, sans-serif; }
    h1, h2, h3, strong { color: #666; }
    blockquote{ background: #bbb; border-radius: 3px; }
    li { border: 2px solid #ccc; border-radius: 5px; list-style-type: none; margin-bottom: 10px }
    a { color: #1B9BE0; }
    </style>
</head>
<body>
   <h1>Your awesome news aggregation site</h1>
   <h2>Latest News</h2>
    @if(count($news))
        {{--We loop all news feed items --}}
        @foreach($news as $each)
            <h3>News from {{$each->title}}:</h3>
            <ul>
            {{-- for each feed item, we get and parse its feed elements --}}
            <?php $feeds = Str::parse_feed($each->feed); ?>
            @if(count($feeds))
                {{-- In a loop, we show all feed elements one by one --}}
                @foreach($feeds as $eachfeed)
                    <li>
                        <strong>{{$eachfeed->title}}</strong><br />
                        <blockquote>{{Str::limit(strip_tags($eachfeed->description),250)}}</blockquote>
                        <strong>Date: {{$eachfeed->pubDate}}</strong><br />
                        <strong>Source: {{HTML::link($eachfeed->link,Str::limit($eachfeed->link,35))}}</strong>

                    </li>
                @endforeach
            @else
                <li>No news found for {{$each->title}}.</li>
            @endif
            </ul>
        @endforeach
    @else
        <p>No News found :(</p>
    @endif

    <hr />

    <h2>Latest Sports News</h2>
    @if(count($sports))
        {{--We loop all news feed items --}}
        @foreach($sports as $each)
            <h3>Sports News from {{$each->title}}:</h3>
            <ul>
            {{-- for each feed item, we get and parse its feed elements --}}
            <?php $feeds = Str::parse_feed($each->feed); ?>
            @if(count($feeds))
                {{-- In a loop, we show all feed elements one by one --}}
                @foreach($feeds as $eachfeed)
                    <li>
                        <strong>{{$eachfeed->title}}</strong><br />
                        <blockquote>{{Str::limit(strip_tags($eachfeed->description),250)}}</blockquote>
                        <strong>Date: {{$eachfeed->pubDate}}</strong><br />
                        <strong>Source: {{HTML::link($eachfeed->link,Str::limit($eachfeed->link,35))}}</strong>
                    </li>
                @endforeach
            @else
                <li>No Sports News found for {{$each->title}}.</li>
            @endif
            </ul>
        @endforeach
    @else
        <p>No Sports News found :(</p>
    @endif

    <hr />

    <h2>Latest Technology News</h2>
    @if(count($technology))
       {{--We loop all news feed items --}}
        @foreach($technology as $each)
            <h3>Technology News from {{$each->title}}:</h3>
            <ul>
            {{-- for each feed item, we get and parse its feed elements --}}
            <?php $feeds = Str::parse_feed($each->feed); ?>
            @if(count($feeds))
                {{-- In a loop, we show all feed elements one by one --}}
                @foreach($feeds as $eachfeed)
                    <li>
                        <strong>{{$eachfeed->title}}</strong><br />
                        <blockquote>{{Str::limit(strip_tags($eachfeed->description),250)}}</blockquote>
                        <strong>Date: {{$eachfeed->pubDate}}</strong><br />
                        <strong>Source: {{HTML::link($eachfeed->link,Str::limit($eachfeed->link,35))}}</strong>
                    </li>
                @endforeach
            @else
                <li>No Technology News found for {{$each->title}}.</li>
            @endif
            </ul>
        @endforeach
    @else
        <p>No Technology News found :(</p>
    @endif

</body>
</html>
  1. 我们为每个类别写了相同的代码三次。此外,在head标签之间进行了一些样式处理,以便页面对最终用户看起来更漂亮。

我们用<hr>标签分隔了每个类别的部分。所有三个部分的工作机制都相同,除了源变量和分组。

我们首先检查每个类别是否存在记录(来自数据库的结果,因为我们可能还没有添加任何新闻源)。如果有结果,就使用 Blade 模板引擎的@foreach()方法循环遍历每条记录。

对于每条记录,我们首先显示反馈的友好名称(我们在保存时定义的),并使用我们刚刚创建的parse_feed()方法解析反馈。

在解析每个反馈后,我们查看是否找到了任何记录;如果找到了,我们再次循环它们。为了保持我们反馈阅读器的整洁,我们使用 PHP 的strip_tags()函数去除了所有 HTML 标签,并使用 Laravel 的Str类的limit()方法将它们限制在最多 250 个字符。

各个反馈项也有自己的标题、日期和源链接,所以我们也在反馈上显示了它们。为了防止链接破坏我们的界面,我们将文本限制在 35 个字符之间写在锚标签之间。

在所有编辑完成后,你应该得到如下输出:

读取和解析外部反馈

摘要

在本章中,我们使用 Laravel 的内置函数和 PHP 的SimpleXML类创建了一个简单的反馈阅读器。我们学会了如何扩展核心库,编写自己的方法,并在生产中使用它们。我们还学会了在查询数据库时如何过滤结果以及如何创建记录。最后,我们学会了如何处理字符串,限制它们,并清理它们。在下一章中,我们将创建一个照片库系统。我们将确保上传的文件是照片。我们还将把照片分组到相册中,并使用 Laravel 的内置关联方法关联相册和照片。

第六章:创建照片库系统

在本章中,我们将使用 Laravel 编写一个简单的照片库系统。我们还将涵盖 Laravel 内置的文件验证、文件上传和hasMany数据库关系机制。我们将使用validation类来验证数据和文件。此外,我们还将涵盖用于处理文件的文件类。本章涵盖以下主题:

  • 创建相册模型

  • 创建图像模型

  • 创建相册

  • 创建照片上传表单

  • 在相册之间移动照片

创建相册表并迁移

我们假设你已经在app/config/目录下的database.php文件中定义了数据库凭据。要构建一个照片库系统,我们需要一个包含两个表albumsimages的数据库。要创建一个新数据库,只需运行以下 SQL 命令:

**CREATE DATABASE laravel_photogallery**

成功创建应用程序的数据库后,我们首先需要创建albums表并将其安装到数据库中。为此,请打开终端,导航到项目文件夹,运行以下命令:

**php artisan migrate:make create_albums_table --table=albums --create**

上述命令将在app/database/migrations下生成一个迁移文件,用于在我们的laravel_photogallery数据库中生成一个名为posts的新 MySQL 表。

为了定义我们的表列,我们需要编辑迁移文件。编辑后,文件应该包含以下代码:

<?php

use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreateAlbumsTable extends Migration {

  /**
  * Run the migrations.
  *
  * @return void
  */
  public function up()
    {
      Schema::create('albums', function(Blueprint $table)
      {
        $table->increments('id')->unsigned();
        $table->string('name');
        $table->text('description');
        $table->string('cover_image');
        $table->timestamps();
      });
    }

  /**
  * Reverse the migrations.
  *
  * @return void
  */
  public function down()
  {
    Schema::drop('albums');
  }
}

保存文件后,我们需要再次使用简单的 artisan 命令来执行迁移:

**php artisan migrate**

如果没有发生错误,请检查laravel_photogallery数据库的albums表及其列。

让我们检查以下列表中的列:

  • id:此列用于存储相册的 ID

  • name:此列用于存储相册的名称

  • description:此列用于存储相册的描述

  • cover_image:此列用于存储相册的封面图像

我们已成功创建了albums表,现在需要编写我们的Album模型。

创建相册模型

如你所知,对于 Laravel 上的任何与数据库操作相关的事情,使用模型是最佳实践。我们将受益于使用 Eloquent ORM。

将以下代码保存为Album.php,放在app/models/目录中:

<?php
class Album extends Eloquent {

  protected $table = 'albums';

  protected $fillable = array('name','description','cover_image');

  public function Photos(){

    return $this->has_many('images');
  }
}

我们使用protected $table变量设置了数据库表名;我们还使用了protected $fillable变量设置了可编辑的列,这是我们在之前章节中已经见过和使用过的。模型中定义的变量足以使用 Laravel 的 Eloquent ORM。我们将在本章的将照片分配给相册部分中介绍public Photos()函数。

我们的Album模型已准备好;现在我们需要一个Image模型和一个分配照片到相册的数据库。让我们创建它们。

使用迁移类创建图像数据库

要为图像创建我们的迁移文件,打开终端,导航到项目文件夹,运行以下命令:

**php artisan migrate:make create_images_table --table=images --create**

如你所知,该命令将在app/database/migrations中生成一个迁移文件。让我们编辑迁移文件;最终代码应该如下所示:

<?php

use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreateImagesTable extends Migration {

  /**
  * Run the migrations.
  *
  * @return void
  */
  public function up()
  {
    Schema::create('images', function(Blueprint $table)
    {
      $table->increments('id')->unsigned();
      $table->integer('album_id')->unsigned();
      $table->string('image');
      $table->string('description');
      $table->foreign('album_id')->references('id')->on('albums')->onDelete('CASCADE')->onUpdate('CASCADE');
      $table->timestamps();
    });
  }

  /**
  * Reverse the migrations.
  *
  * @return void
  */
  public function down()
  {
    Schema::drop('images');
  }
}

编辑迁移文件后,运行以下迁移命令:

**php artisan migrate**

如你所知,该命令创建了images表及其列。如果没有发生错误,请检查laravel_photogallery数据库的users表及其列。

让我们检查以下列表中的列:

  • id:此列用于存储图像的 ID

  • album_id:此列用于存储图像所属相册的 ID

  • description:此列用于存储图像的描述

  • image:此列用于存储图像的路径

我们需要解释一下这个迁移文件的另一件事。如你在迁移代码中所见,有一个foreign键。当我们需要链接两个表时,我们使用foreign键。我们有一个albums表,每个相册都会有图像。如果从数据库中删除相册,你也希望删除其所有图像。

创建一个 Image 模型

我们已经创建了images表。所以,你知道,我们需要一个模型来在 Laravel 上操作数据库表。为了创建它,将以下代码保存为 Image.php 在app/models/目录中:

class Images extends Eloquent {

  protected $table = 'images';

  protected $fillable = array('album_id','description','image');

}

我们的Image模型已经准备好了;现在我们需要一个控制器来在我们的数据库上创建相册。让我们来创建它。

创建相册

正如你从本书的前几章中所了解的,Laravel 拥有一个很棒的 RESTful 控制器机制。我们将继续使用它来在开发过程中保持代码简单和简洁。在接下来的章节中,我们将介绍另一种很棒的控制器/路由方法,名为资源控制器

为了列出、创建和删除相册,我们需要在我们的控制器中添加一些函数。为了创建它们,将以下代码保存为AlbumsController.phpapp/controllers/目录中:

<?php

class AlbumsController extends BaseController{

  public function getList()
  {
    $albums = Album::with('Photos')->get();
    return View::make('index')
    ->with('albums',$albums);
  }
  public function getAlbum($id)
  {
    $album = Album::with('Photos')->find($id);
    return View::make('album')
    ->with('album',$album);
  }
  public function getForm()
  {
    return View::make('createalbum');
  }
  public function postCreate()
  {
    $rules = array(

      'name' => 'required',
      'cover_image'=>'required|image'

    );

    $validator = Validator::make(Input::all(), $rules);
    if($validator->fails()){

      return Redirect::route('create_album_form')
      ->withErrors($validator)
      ->withInput();
    }

    $file = Input::file('cover_image');
    $random_name = str_random(8);
    $destinationPath = 'albums/';
    $extension = $file->getClientOriginalExtension();
    $filename=$random_name.'_cover.'.$extension;
    $uploadSuccess = Input::file('cover_image')
    ->move($destinationPath, $filename);
    $album = Album::create(array(
      'name' => Input::get('name'),
      'description' => Input::get('description'),
      'cover_image' => $filename,
    ));

    return Redirect::route('show_album',array('id'=>$album->id));
  }

  public function getDelete($id)
  {
    $album = Album::find($id);

    $album->delete();

    return Redirect::route('index');
  }
}

postCreate()函数首先验证表单提交的数据。我们将在下一节中介绍验证。如果数据验证成功,我们将重命名封面图像并使用新文件名上传它,因为代码会覆盖具有相同名称的文件。

getDelete()函数正在从数据库中删除相册以及分配的图像(存储在images表中)。请记住以下迁移文件代码:

$table->foreign('album_id')->references('id')->on('albums')->onDelete('CASCADE')->onUpdate('CASCADE');

在创建我们的模板之前,我们需要定义路由。因此,打开app文件夹中的routes.php文件,并用以下代码替换它:

<?php
Route::get('/', array('as' => 'index','uses' => 'AlbumsController@getList'));
Route::get('/createalbum', array('as' => 'create_album_form','uses' => 'AlbumsController@getForm'));
Route::post('/createalbum', array('as' => 'create_album','uses' => 'AlbumsController@postCreate'));
Route::get('/deletealbum/{id}', array('as' => 'delete_album','uses' => 'AlbumsController@getDelete'));
Route::get('/album/{id}', array('as' => 'show_album','uses' => 'AlbumsController@getAlbum'));

现在,我们需要一些模板文件来显示、创建和列出相册。首先,我们应该创建索引模板。为了创建它,将以下代码保存为index.blade.phpapp/views/目录中:

<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <title>Awesome Albums</title>
    <!-- Latest compiled and minified CSS -->
    <link href="//netdna.bootstrapcdn.com/bootstrap/3.0.0-rc1/css/bootstrap.min.css" rel="stylesheet">

    <!-- Latest compiled and minified JavaScript -->
    <script src="//netdna.bootstrapcdn.com/bootstrap/3.0.0-rc1/js/bootstrap.min.js"></script>
    <style>
      body {
        padding-top: 50px;
      }
      .starter-template {
        padding: 40px 15px;
      text-align: center;
      }
    </style>
  </head>
  <body>
    <div class="navbar navbar-inverse navbar-fixed-top">
      <div class="container">
      <button type="button" class="navbar-toggle"data-toggle="collapse" data-target=".nav-collapse">
        <span class="icon-bar"></span>
        <span class="icon-bar"></span>
        <span class="icon-bar"></span>
      </button>
      <a class="navbar-brand" href="/">Awesome Albums</a>
      <div class="nav-collapse collapse">
        <ul class="nav navbar-nav">
          <li><a href="{{URL::route('create_album_form')}}">Create New Album</a></li>
        </ul>
      </div><!--/.nav-collapse -->
    </div>
    </div>

      <div class="container">

        <div class="starter-template">

        <div class="row">
          @foreach($albums as $album)
            <div class="col-lg-3">
              <div class="thumbnail" style="min-height: 514px;">
                <img alt="{{$album->name}}" src="/albums/{{$album->cover_image}}">
                <div class="caption">
                  <h3>{{$album->name}}</h3>
                  <p>{{$album->description}}</p>
                  <p>{{count($album->Photos)}} image(s).</p>
                  <p>Created date:  {{ date("d F Y",strtotime($album->created_at)) }} at {{date("g:ha",strtotime($album->created_at)) }}</p>
                  <p><a href="{{URL::route('show_album', array('id'=>$album->id))}}" class="btn btn-big btn-default">Show Gallery</a></p>
                </div>
              </div>
            </div>
          @endforeach
        </div>

      </div><!-- /.container -->
    </div>

  </body>
</html>

为创建相册添加模板

正如你在以下代码中所看到的,我们更喜欢使用 Twitter 的 bootstrap CSS框架。这个框架允许你快速创建有用、响应式和多浏览器支持的界面。接下来,我们需要为创建相册创建一个模板。为了创建它,将以下代码保存为createalbum.blade.phpapp/views/目录中:

<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <title>Create an Album</title>
    <!-- Latest compiled and minified CSS -->
    <link href="//netdna.bootstrapcdn.com/bootstrap/3.0.0-rc1/css/bootstrap.min.css" rel="stylesheet">

    <!-- Latest compiled and minified JavaScript -->
    <script src="//netdna.bootstrapcdn.com/bootstrap/3.0.0-rc1/js/bootstrap.min.js"></script>
  </head>
  <body>
    <div class="navbar navbar-inverse navbar-fixed-top">
      <div class="container">
        <button type="button" class="navbar-toggle"data-toggle="collapse" data-target=".nav-collapse">
          <span class="icon-bar"></span>
          <span class="icon-bar"></span>
          <span lclass="icon-bar"></span>
        </button>
        <a class="navbar-brand" href="/">Awesome Albums</a>
        <div class="nav-collapse collapse">
          <ul class="nav navbar-nav">
            <li class="active"><ahref="{{URL::route('create_album_form')}}">CreateNew Album</a></li>
          </ul>
        </div><!--/.nav-collapse -->
      </div>
    </div>
    <div class="container" style="text-align: center;">
      <div class="span4" style="display: inline-block;margin-top:100px;">

        @if($errors->has())
          <div class="alert alert-block alert-error fade in"id="error-block">
             <?php
             $messages = $errors->all('<li>:message</li>');
            ?>
            <button type="button" class="close"data-dismiss="alert">×</button>

            <h4>Warning!</h4>
            <ul>
              @foreach($messages as $message)
                {{$message}}
              @endforeach

            </ul>
          </div>
        @endif

        <form name="createnewalbum" method="POST"action="{{URL::route('create_album')}}"enctype="multipart/form-data">
          <fieldset>
            <legend>Create an Album</legend>
            <div class="form-group">
              <label for="name">Album Name</label>
              <input name="name" type="text" class="form-control"placeholder="Album Name"value="{{Input::old('name')}}">
            </div>
            <div class="form-group">
              <label for="description">Album Description</label>
              <textarea name="description" type="text"class="form-control" placeholder="Albumdescription">{{Input::old('descrption')}}</textarea>
            </div>
            <div class="form-group">
              <label for="cover_image">Select a Cover Image</label>
              {{Form::file('cover_image')}}
            </div>
            <button type="submit" class="btnbtn-default">Create!</button>
          </fieldset>
        </form>
      </div>
    </div> <!-- /container -->
  </body>
</html>

该模板创建了一个基本的上传表单,并显示了从控制器端传递的验证错误。我们只需要再创建一个模板文件来列出相册图像。因此,为了创建它,将以下代码保存为album.blade.phpapp/views/目录中:

<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <title>{{$album->name}}</title>
    <!-- Latest compiled and minified CSS -->
    <link href="//netdna.bootstrapcdn.com/bootstrap/3.0.0-rc1/css/bootstrap.min.css" rel="stylesheet">

    <!-- Latest compiled and minified JavaScript -->
    <script src="//netdna.bootstrapcdn.com/bootstrap/3.0.0-rc1/js/bootstrap.min.js"></script>
    <style>
      body {
        padding-top: 50px;
      }
      .starter-template {
        padding: 40px 15px;
        text-align: center;
      }
    </style>
  </head>
  <body>
    <div class="navbar navbar-inverse navbar-fixed-top">
      <div class="container">
        <button type="button" class="navbar-toggle"data-toggle="collapse" data-target=".nav-collapse">
          <span class="icon-bar"></span>
          <span class="icon-bar"></span>
          <span class="icon-bar"></span>
        </button>
        <a class="navbar-brand" href="/">Awesome Albums</a>
        <div class="nav-collapse collapse">
          <ul class="nav navbar-nav">
            <li><a href="{{URL::route('create_album_form')}}">Create New Album</a></li>
          </ul>
        </div><!--/.nav-collapse -->
     </div>
    </div>
    <div class="container">

      <div class="starter-template">
        <div class="media">
          <img class="media-object pull-left"alt="{{$album->name}}" src="/albums/{{$album->cover_image}}" width="350px">
          <div class="media-body">
            <h2 class="media-heading" style="font-size: 26px;">Album Name:</h2>
            <p>{{$album->name}}</p>
          <div class="media">
          <h2 class="media-heading" style="font-size: 26px;">AlbumDescription :</h2>
          <p>{{$album->description}}<p>
          <a href="{{URL::route('add_image',array('id'=>$album->id))}}"><button type="button"class="btn btn-primary btn-large">Add New Image to Album</button></a>
          <a href="{{URL::route('delete_album',array('id'=>$album->id))}}" onclick="return confirm('Are yousure?')"><button type="button"class="btn btn-danger btn-large">Delete Album</button></a>
        </div>
      </div>
    </div>
    </div>
      <div class="row">
        @foreach($album->Photos as $photo)
          <div class="col-lg-3">
            <div class="thumbnail" style="max-height: 350px;min-height: 350px;">
              <img alt="{{$album->name}}" src="/albums/{{$photo->image}}">
              <div class="caption">
                <p>{{$photo->description}}</p>
                <p><p>Created date:  {{ date("d F Y",strtotime($photo->created_at)) }} at {{ date("g:ha",strtotime($photo->created_at)) }}</p></p>
                <a href="{{URL::route('delete_image',array('id'=>$photo->id))}}" onclick="return confirm('Are you sure?')"><button type="button" class="btnbtn-danger btn-small">Delete Image </button></a>
              </div>
            </div>
          </div>
        @endforeach
      </div>
    </div>

  </body>
</html>

正如你可能记得的,我们在模型端使用了hasMany() Eloquent 方法。在控制器端,我们使用以下函数:

**$albums = Album::with('Photos')->get();**

该代码在数组中获取了属于相册的整个图像数据。因此,我们在以下模板中使用foreach循环:

@foreach($album->Photos as $photo)
  <div class="col-lg-3">
    <div class="thumbnail" style="max-height: 350px;min-height: 350px;">
    <img alt="{{$album->name}}" src="/albums/{{$photo->image}}">
      <div class="caption">
        <p>{{$photo->description}}</p>
        <p><p>Created date:  {{ date("d F Y",strtotime($photo->created_at)) }} at {{ date("g:ha",strtotime($photo->created_at)) }}</p></p>
        <a href="{{URL::route('delete_image',array('id'=>$photo->id))}}" onclick="return confirm('Are yousure?')"><button type="button" class="btnbtn-danger btn-small">Delete Image</button></a>
      </div>
    </div>
  </div>
@endforeach

创建一个照片上传表单

现在我们需要创建一个照片上传表单。我们将上传照片并将它们分配到相册中。让我们首先设置路由;打开app文件夹中的routes.php文件,并添加以下代码:

Route::get('/addimage/{id}', array('as' => 'add_image','uses' => 'ImagesController@getForm'));
Route::post('/addimage', array('as' => 'add_image_to_album','uses' => 'ImagesController@postAdd'));
Route::get('/deleteimage/{id}', array('as' => 'delete_image','uses' => 'ImagesController@getDelete'));

我们需要一个照片上传表单的模板。为了创建它,将以下代码保存为addimage.blade.phpapp/views/目录中:

<!doctype html>
  <html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <title>Laravel PHP Framework</title>
    <!-- Latest compiled and minified CSS -->
    <link href="//netdna.bootstrapcdn.com/bootstrap/3.0.0-rc1/css/bootstrap.min.css" rel="stylesheet">

    <!-- Latest compiled and minified JavaScript -->
    <script src="//netdna.bootstrapcdn.com/bootstrap/3.0.0-rc1/js/bootstrap.min.js"></script>
  </head>
  <body>

    <div class="container" style="text-align: center;">
      <div class="span4" style="display: inline-block;margin-top:100px;">
        @if($errors->has())
          <div class="alert alert-block alert-error fade in"id="error-block">
            <?php
            $messages = $errors->all('<li>:message</li>');
            ?>
            <button type="button" class="close"data-dismiss="alert">×</button>

            <h4>Warning!</h4>
            <ul>
              @foreach($messages as $message)
                {{$message}}
              @endforeach

            </ul>
          </div>
        @endif
        <form name="addimagetoalbum" method="POST"action="{{URL::route('add_image_to_album')}}"enctype="multipart/form-data">
          <input type="hidden" name="album_id"value="{{$album->id}}" />
          <fieldset>
            <legend>Add an Image to {{$album->name}}</legend>
            <div class="form-group">
              <label for="description">Image Description</label>
              <textarea name="description" type="text"class="form-control" placeholder="Imagedescription"></textarea>
            </div>
            <div class="form-group">
              <label for="image">Select an Image</label>
              {{Form::file('image')}}
            </div>
            <button type="submit" class="btnbtn-default">Add Image!</button>
          </fieldset>
        </form>
      </div>
    </div> <!-- /container -->
  </body>
</html>

在创建模板之前,我们需要编写我们的控制器。因此,将以下代码保存为ImageController.phpapp/controllers/目录中:

<?php
class ImagesController extends BaseController{

  public function getForm($id)
  {
    $album = Album::find($id);
    return View::make('addimage')
    ->with('album',$album);
  }

  public function postAdd()
  {
    $rules = array(

      'album_id' => 'required|numeric|exists:albums,id',
      'image'=>'required|image'

    );

    $validator = Validator::make(Input::all(), $rules);
    if($validator->fails()){

      return Redirect::route('add_image',array('id' =>Input::get('album_id')))
      ->withErrors($validator)
      ->withInput();
    }

    $file = Input::file('image');
    $random_name = str_random(8);
    $destinationPath = 'albums/';
    $extension = $file->getClientOriginalExtension();
    $filename=$random_name.'_album_image.'.$extension;
    $uploadSuccess = Input::file('image')->move($destinationPath, $filename);
    Image::create(array(
      'description' => Input::get('description'),
      'image' => $filename,
      'album_id'=> Input::get('album_id')
    ));

    return Redirect::route('show_album',array('id'=>Input::get('album_id')));
  }
  public function getDelete($id)
  {
    $image = Image::find($id);
    $image->delete();
    return Redirect::route('show_album',array('id'=>$image->album_id));
  }
}

控制器有三个函数;第一个是getForm()函数。这个函数基本上显示了我们的照片上传表单。第二个函数验证并将数据插入数据库。我们将在下一节中解释验证和插入函数。第三个是getDelete()函数。这个函数基本上从数据库中删除图像记录。

验证照片

Laravel 拥有强大的验证库,在本书中已经多次提到。我们在控制器中验证数据如下:

$rules = array(

  'album_id' => 'required|numeric|exists:albums,id',
  'image'=>'required|image'

);

$validator = Validator::make(Input::all(), $rules);
if($validator->fails()){

  return Redirect::route('add_image',array('id' =>Input::get('album_id')))
  ->withErrors($validator)
  ->withInput();
}

让我们来看一下代码。我们在array中定义了一些规则。在rules数组中有两个验证规则。第一个规则如下:

'album_id' => 'required|numeric|exists:albums,id'

前面的规则意味着album_id字段是必需的(必须在表单中发布),必须是数值,并且必须存在于albums表的id列中,因为我们想要将图片分配给albums。第二条规则如下:

'image'=>'required|image'

前面的规则意味着image字段是必需的(必须在表单中发布),其内容必须是图片。然后我们使用以下代码检查发布的表单数据:

$validator = Validator::make(Input::all(), $rules);

验证函数需要两个变量。第一个是我们需要验证的数据。在这种情况下,我们使用Input::all()方法进行设置,这意味着我们需要验证发布的表单数据。第二个是rules变量。rules变量必须设置为一个数组,如下所示:

$rules = array(

  'album_id' => 'required|numeric|exists:albums,id',
  'image'=>'required|image'

);

Laravel 的验证类带有许多预定义规则。您可以在laravel.com/docs/validation#available-validation-rules上看到所有可用验证规则的更新列表。

有时,我们需要验证特定的 MIME 类型,例如JPEG、BMP、ORG 和 PNG。您可以轻松地设置此类验证的验证规则,如下所示:

'image' =>'required|mimes:jpeg,bmp,png'

然后我们使用以下代码检查验证过程:

if($validator->fails()){

  return Redirect::route('add_image',array('id' =>Input::get('album_id')))
  ->withErrors($validator)
  ->withInput();
}

如果验证失败,我们将浏览器重定向到图片上传表单。然后,我们使用以下代码在模板文件中显示规则:

@if($errors->has())
  <div class="alert alert-block alert-error fade in"id="error-block">
    <?php
    $messages = $errors->all('<li>:message</li>');
    ?>
    <button type="button" class="close"data-dismiss="alert">×</button>

    <h4>Warning!</h4>
    <ul>
      @foreach($messages as $message)
        {{$message}}
      @endforeach

    </ul>
  </div>
@endif

将照片分配给相册

postAdd()函数用于处理请求,在数据库中创建新的图片记录。我们使用以下先前提到的方法获取作者的 ID:

Auth::user()->id

使用以下方法,我们将当前用户与博客文章进行关联。我们在查询中有一个新的方法,如下所示:

Posts::with('Author')->…

我们在相册模型中定义了一个public Photos()函数,使用以下代码:

public function Photos(){

  return $this->hasMany('images','album_id');
}

hasMany()方法是一个用于创建表之间关系的 Eloquent 函数。基本上,该函数有一个required变量和一个可选变量。第一个变量(required)用于定义目标模型。第二个可选变量用于定义当前模型表的源列。在这种情况下,我们将相册的 ID 存储在images表的album_id列中。因此,我们需要在函数中将第二个变量定义为album_id。如果您的 ID 不遵循约定,则第二个参数是必需的。使用这种方法,我们可以同时将相册信息和分配的图片数据传递给模板。

正如您在第四章构建个人博客中所记得的,我们可以在foreach循环中列出关系数据。让我们快速查看一下我们模板文件中的图像列表部分的代码,该文件位于app/views/album.blade.php中:

@foreach($album->Photos as $photo)

  <div class="col-lg-3">
    <div class="thumbnail" style="max-height: 350px;min-height: 350px;">
    <img alt="{{$album->name}}" src="/albums/{{$photo->image}}">
      <div class="caption">
        <p>{{$photo->description}}</p>
        <p><p>Created date:  {{ date("d F Y",strtotime($photo->created_at)) }} at {{ date("g:ha",strtotime($photo->created_at)) }}</p></p>
        <a href="{{URL::route('delete_image',array('id'=>$photo->id))}}" onclick="return confirm('Are yousure?')"><button type="button" class="btnbtn-danger btn-small">Delete Image</button></a>
      </div>
    </div>
  </div>

@endforeach

在相册之间移动照片

在相册之间移动照片是管理相册图像的一个很好的功能。许多相册系统都具有此功能。因此,我们可以使用 Laravel 轻松编写它。我们需要一个表单和控制器函数来将此功能添加到我们的相册系统中。让我们首先编写控制器函数。打开位于app/controllers/中的ImagesController.php文件,并在其中添加以下代码:

public function postMove()
{
  $rules = array(

    'new_album' => 'required|numeric|exists:albums,id',
    'photo'=>'required|numeric|exists:images,id'

  );

  $validator = Validator::make(Input::all(), $rules);
  if($validator->fails()){

    return Redirect::route('index');
  }
  $image = Image::find(Input::get('photo'));
  $image->album_id = Input::get('new_album');
  $image->save();
  return Redirect::route('show_album',array('id'=>Input::get('new_album')));
}

如您在前面的代码中所看到的,我们再次使用Validation类。让我们检查规则。第一个规则如下:

'new_album' => 'required|numeric|exists:albums,id'

前面的规则意味着new_album字段是required(必须在表单中发布),必须是数值,并且存在于albums表的id列中。我们想要将图片分配给相册,所以图片必须存在。第二条规则如下:

'photo'=>'required|numeric|exists:images,id'

前面的规则意味着photo字段是required(必须在表单中发布),必须是数值,并且存在于images表的id列中。

成功验证后,我们会更新photos字段的album_id列,并使用以下代码将浏览器重定向到显示新相册照片的页面:

$image = Image::find(Input::get('photo'));
$image->album_id = Input::get('new_album');
$image->save();
return Redirect::route('show_album',array('id'=>Input::get('new_album')));

Images控制器的最终代码应如下所示:

<?php

class ImagesController extends BaseController{

  public function getForm($id)
  {
    $album = Album::find($id);

    return View::make('addimage')
    ->with('album',$album);
  }

  public function postAdd()
  {
    $rules = array(

      'album_id' => 'required|numeric|exists:albums,id',
      'image'=>'required|image'

    );

    $validator = Validator::make(Input::all(), $rules);
    if($validator->fails()){

      return Redirect::route('add_image',array('id' =>Input::get('album_id')))
      ->withErrors($validator)
      ->withInput();
    }

    $file = Input::file('image');
    $random_name = str_random(8);
    $destinationPath = 'albums/';
    $extension = $file->getClientOriginalExtension();
    $filename=$random_name.'_album_image.'.$extension;
    $uploadSuccess = Input::file('image')->move($destinationPath, $filename);
    Image::create(array(
      'description' => Input::get('description'),
      'image' => $filename,
      'album_id'=> Input::get('album_id')
    ));

    return Redirect::route('show_album',array('id'=>Input::get('album_id')));
  }
  public function getDelete($id)
  {
    $image = Image::find($id);

    $image->delete();

    return Redirect::route('show_album',array('id'=>$image->album_id));
  }
  public function postMove()
  {
    $rules = array(
      'new_album' => 'required|numeric|exists:albums,id',
      'photo'=>'required|numeric|exists:images,id'
    );
    $validator = Validator::make(Input::all(), $rules);
    if($validator->fails()){

      return Redirect::route('index');
    }
    $image = Image::find(Input::get('photo'));
    $image->album_id = Input::get('new_album');
    $image->save();
    return Redirect::route('show_album',array('id'=>Input::get('new_album')));
  }
}

我们的控制器已经准备好了,所以我们需要在app/routes.php中设置更新后的表单路由。打开文件并添加以下代码:

Route::post('/moveimage', array('as' => 'move_image', 'uses' => 'ImagesController@postMove'));

app/routes.php中的最终代码应如下所示:

<?php
Route::get('/', array('as' => 'index', 'uses' =>
  'AlbumsController@getList'));
Route::get('/createalbum', array('as' => 'create_album_form',
  'uses' => 'AlbumsController@getForm'));
Route::post('/createalbum', array('as' => 'create_album',
  'uses' => 'AlbumsController@postCreate'));
Route::get('/deletealbum/{id}', array('as' => 'delete_album',
  'uses' => 'AlbumsController@getDelete'));
Route::get('/album/{id}', array('as' => 'show_album', 'uses' =>
  'AlbumsController@getAlbum'));
Route::get('/addimage/{id}', array('as' => 'add_image', 'uses' =>
  'ImagesController@getForm'));
Route::post('/addimage', array('as' => 'add_image_to_album',
  'uses' => 'ImagesController@postAdd'));
Route::get('/deleteimage/{id}', array('as' => 'delete_image',
'uses' => 'ImagesController@getDelete'));
Route::post('/moveimage', array('as' => 'move_image',
'uses' => 'ImagesController@postMove'));

创建更新表单

现在我们需要在模板文件中创建更新表单。打开位于app/views/album.blade.php中的模板文件,并将foreach循环更改如下:

@foreach($album->Photos as $photo)
  <div class="col-lg-3">
    <div class="thumbnail" style="max-height: 350px;min-height: 350px;">
      <img alt="{{$album->name}}" src="/albums/{{$photo->image}}">
      <div class="caption">
        <p>{{$photo->description}}</p>
        <p>Created date:  {{ date("d F Y",strtotime($photo->created_at)) }}at {{ date("g:ha",strtotime($photo->created_at)) }}</p>
        <a href="{{URL::route('delete_image',array('id'=>$photo->id))}}" onclick="returnconfirm('Are you sure?')"><button type="button"class="btn btn-danger btn-small">Delete Image</button></a>
        <p>Move image to another Album :</p>
        <form name="movephoto" method="POST"action="{{URL::route('move_image')}}">
          <select name="new_album">
            @foreach($albums as $others)
              <option value="{{$others->id}}">{{$others->name}}</option>
            @endforeach
          </select>
          <input type="hidden" name="photo"value="{{$photo->id}}" />
          <button type="submit" class="btn btn-smallbtn-info" onclick="return confirm('Are you sure?')">Move Image</button>
        </form>
      </div>
    </div>
  </div>
@endforeach

摘要

在本章中,我们使用 Laravel 的内置函数和 Eloquent 数据库驱动创建了一个简单的相册系统。我们学会了如何验证数据,以及 Eloquent 中强大的数据关联方法 hasMany。在接下来的章节中,我们将学习如何处理更复杂的表格和关联数据以及关联类型。

第七章:创建一个通讯系统

在本章中,我们将介绍一个高级的通讯系统,它将使用 Laravel 的queueemail库。在本节之后,我们将学习如何设置和触发排队任务,以及如何解析电子邮件模板并向订阅者发送大量电子邮件。本章涵盖的主题有:

  • 创建一个数据库并迁移订阅者的表

  • 创建一个订阅者模型

  • 创建我们的订阅表单

  • 验证和处理表单

  • 创建一个处理电子邮件的队列系统

  • 使用 Email 类来处理队列中的电子邮件

  • 测试系统

  • 直接使用队列发送电子邮件

在本章中,我们将使用第三方服务,这将需要访问你的脚本,所以在继续之前,请确保你的项目可以在线访问。

创建一个数据库并迁移订阅者表

成功安装 Laravel 4 并从app/config/database.php中定义数据库凭据后,创建一个名为chapter7的数据库。

创建数据库后,打开你的终端,导航到你的项目文件夹,并运行以下命令:

**php artisan migrate:make create_subscribers_table --table=subscribers –-create**

上述命令将为我们生成一个名为subscribers的新 MySQL 迁移。现在转到app/database/中的migrations文件夹,并打开刚刚由上述命令创建的迁移文件,并按照下面的代码更改其内容:

<?php
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreateSubscribersTable extends Migration {

  /**
   * Run the migrations.
   *
   * @return void
   */
  public function up()
  {
    Schema::create('subscribers', function(Blueprint $table)
    {
      $table->increments('id');
      $table->string('email,100)->default('');
      $table->timestamps();
    });
  }

  /**
  * Reverse the migrations.
  *
  * @return void
  */
  public function down()
  {
    Schema::drop('subscribers');
  }
}

对于本章,我们只需要email列,它将保存订阅者的电子邮件地址。我将这一列设置为最多 100 个字符长,数据类型为VARCHAR,并且不允许为空。

保存文件后,运行以下命令执行迁移:

**php artisan migrate**

如果没有发生错误,你已经准备好进行项目的下一步了。

创建一个订阅者模型

为了从 Eloquent ORM 中受益,最佳实践是创建一个模型。

将以下代码保存在app/models/下的subscribers.php文件中:

<?php
Class Subscribers Extends Eloquent{
  protected $table = 'subscribers';
  protected $fillable = array('email');
}

我们使用变量$table设置表名,并使用变量$fillable设置用户必须填写值的列。现在我们的模型已经准备好了,我们可以继续下一步,开始创建我们的控制器和表单。

创建我们的订阅表单

现在我们应该创建一个表单来保存记录到数据库并指定它的属性。

  1. 首先,打开你的终端,输入以下命令:
php artisan controller:make SubscribersController

这个命令将为你在app/controllers目录中生成一个SubscribersController.php文件,并在其中添加一些空方法。

注意

artisan命令生成的默认控制器方法不是 RESTful 的。

  1. 现在,打开app/routes.php并添加以下代码:
//We define a RESTful controller and all its via route//directly
Route::controller('subscribers', 'SubscribersController');

我们可以使用controller()方法一次性定义控制器上声明的所有操作,而不是逐个定义所有操作。如果你的方法名可以直接用作getpost操作,使用controller()方法可以节省大量时间。第一个参数设置控制器的URI(统一资源标识符),第二个参数定义了控制器文件夹中将要访问和定义的类。

注意

像这样设置的控制器自动是 RESTful 的。

  1. 现在,让我们创建表单的控制器。删除自动生成的类中的所有方法,并在你的控制器文件中添加以下代码:
//The method to show the form to add a new feed
public function getIndex() {
  //We load a view directly and return it to be served
  return View::make('subscribe_form');
}

首先,我们定义了这个过程。这里很简单;我们将方法命名为getCreate(),因为我们希望我们的Create方法是 RESTful 的。我们简单地加载了一个视图文件,我们将在下一步直接生成。

  1. 现在让我们创建我们的视图文件。在这个例子中,我使用了 jQuery 的 Ajax POST 技术。将这个文件保存为subscribe_form.blade.php,放在app/views/下:
<!doctype html>
<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <title>Subscribe to Newsletter</title>
    <style>
      /*Some Little Minor CSS to tidy up the form*/
      body{margin:0;font-family:Arial,Tahoma,sans-serif;text-align:center;padding-top:60px;color:#666;font-size:24px}
      input{font-size:18px}
      input[type=text]{width:300px}
      div.content{padding-top:24px;font-weight:700;font-size:24px}
      .success{color:#0b0}
      .error{color:#b00}
    </style>
  </head>
  <body>

    {{-- Form Starts Here --}}
    {{Form::open(array('url'=> URL::to('subscribers/submit'),'method' => 'post'))}}
    <p>Simple Newsletter Subscription</p>
    {{Form::text('email',null,array('placeholder'=>'Type your E-mail address here'))}}
    {{Form::submit('Submit!')}}

    {{Form::close()}}
    {{-- Form Ends Here --}}

    {{-- This div will show the ajax response --}}
    <div class="content"></div>
    {{-- Because it'll be sent over AJAX, We add thejQuery source --}}
    {{ HTML::script('http://code.jquery.com/jquery-1.8.3.min.js') }}
    <script type="text/javascript">
      //Even though it's on footer, I just like to make//sure that DOM is ready
      $(function(){
        //We hide de the result div on start
        $('div.content').hide();
        //This part is more jQuery Related. In short, we //make an Ajax post request and get the response//back from server
        $('input[type="submit"]').click(function(e){
          e.preventDefault();
          $.post('/subscribers/submit', {
            email: $('input[name="email"]').val()
          }, function($data){
            if($data=='1') {
              $('div.content').hide().removeClass('success error').addClass('success').html('You\'ve successfully subscribed to ournewsletter').fadeIn('fast');
            } else {
              //This part echos our form validation errors
              $('div.content').hide().removeClass('success error').addClass('error').html('There has been an error occurred:<br /><br />'+$data).fadeIn('fast');
            }
          });
        });
        //We prevented to submit by pressing enter or anyother way
        $('form').submit(function(e){
          e.preventDefault();
          $('input[type="submit"]').click();
        });
      });
    </script>
  </body>
</html>

上述代码将生成一个简单的表单,如下截图所示:

创建我们的订阅表单

现在我们的表单已经准备好了,我们可以继续并处理表单。

验证和处理表单

现在我们有了表单,我们需要验证和存储数据。我们还需要检查请求是否是 Ajax 请求。此外,我们需要使用 Ajax 方法将成功的代码或错误消息返回到表单,以便最终用户可以理解后端发生了什么。

将数据保存在SubscribersController.php中的app/controllers/

//This method is to process the form
public function postSubmit() {

  //we check if it's really an AJAX request
  if(Request::ajax()) {

    $validation = Validator::make(Input::all(), array(
      //email field should be required, should be in an email//format, and should be unique
      'email' => 'required|email|unique:subscribers,email'
    )
    );

    if($validation->fails()) {
      return $validation->errors()->first();
    } else {

      $create = Subscribers::create(array(
        'email' => Input::get('email')
      ));

      //If successful, we will be returning the '1' so the form//understands it's successful
      //or if we encountered an unsuccessful creation attempt,return its info
      return $create?'1':'We could not save your address to oursystem, please try again later';
    }

  } else {
    return Redirect::to('subscribers');
  }
}

以下几点解释了前面的代码:

  1. 使用Request类的ajax()方法,你可以检查请求是否是 Ajax 请求。如果不是 Ajax 请求,我们将被重定向回我们的订阅者页面(表单本身)。

  2. 如果是有效的请求,那么我们将使用Validation类的make()方法运行我们的表单。在这个例子中,我直接编写了规则,但最佳实践是在模型中设置它们,并直接调用它们到控制器。规则required检查字段是否已填写。规则email检查输入是否是有效的电子邮件格式,最后,规则unique帮助我们知道值是否已经在行中或不在。

  3. 如果表单验证失败,我们直接返回第一个错误消息。返回的内容将是 Ajax 的响应,将被回显到我们的表单页面中。由于错误消息是自动生成的有意义的文本消息,所以可以直接在我们的示例中使用。这条消息将显示所有验证错误。例如,它将回显字段是否不是有效的电子邮件地址,或者电子邮件是否已经提交到数据库中。

  4. 如果表单验证通过,我们尝试使用 Laravel 的 Eloquent ORM 的create()方法将电子邮件添加到我们的数据库中。

为基本的电子邮件发送创建一个队列系统

Laravel 4 中的队列是该框架提供的最好的功能之一。想象一下你有一个长时间的过程,比如调整所有图片的大小,发送大量电子邮件,或者大量数据库操作。当你处理这些时,它们会花费时间。那么为什么我们要等待呢?相反,我们将把这些过程放入队列中。使用 Laravel v4,这是相当容易管理的。在本节中,我们将创建一个简单的队列,并循环遍历电子邮件,尝试向每个订阅者发送电子邮件,使用以下步骤:

  1. 首先,我们需要一个队列驱动程序。这可以是Amazon SQSBeanstalkdIron IO。我选择了 Iron IO,因为它目前是唯一支持 push 队列的队列驱动程序。然后我们需要从 packagist 获取包。将"iron-io/iron_mq": "dev-master"添加到composer.jsonrequire键中。它应该看起来像以下代码:
"require": {
     "laravel/framework": "4.0.*",
     "iron-io/iron_mq": "dev-master"
},
  1. 现在,你应该运行以下命令来更新/下载新的包:
**php composer.phar update**

  1. 我们需要一个来自 Laravel 官方支持的队列服务的账户。在这个例子中,我将使用免费的Iron.io服务。

  2. 首先,注册网站iron.io

  3. 其次,在登录后,创建一个名为laravel的项目。

  4. 然后,点击你的项目。有一个关键图标,会给你项目的凭据。点击它,它会提供给你project_idtoken

  5. 现在导航到app/config/queue.php,并将默认的键驱动更改为 iron。

在我们打开的queue文件中,有一个名为iron的键,你将使用它来填写凭据。在那里提供你的tokenproject_id信息,对于queue键,输入laravel

  1. 现在,打开你的终端并输入以下命令:
**php artisan queue:subscribe laravel
  http://your-site-url/queue/push**

  1. 如果一切顺利,你将得到以下输出:
**Queue subscriber added: http://your-site-url/queue/push**

  1. 现在,当你在 Iron.io 项目页面上检查队列标签时,你会看到一个由 Laravel 生成的新的push队列。因为它是一个 push 队列,当队列到达时间时,队列会调用我们。

  2. 现在我们需要一些方法来捕获push请求,对其进行编组并触发它。

  3. 首先,我们需要一个get方法来触发push队列(模拟触发队列的代码)。

将以下代码添加到app文件夹中的routes.php文件中:

       //This code will trigger the push request
       Route::get('queue/process',function(){
         Queue::push('SendEmail');
         return 'Queue Processed Successfully!';
       });

这段代码将向一个名为SendEmail的类发出push请求,我们将在后续步骤中创建该类。

  1. 现在我们需要一个监听器来管理队列。将以下代码添加到app文件夹中的routes.php文件中:
//When the push driver sends us back, we will have to
  //marshal and process the queue.
Route::post('queue/push',function(){
  return Queue::marshal();
});

这段代码将从我们的队列驱动程序获取push请求,然后将其放入队列并运行。

我们需要一个类来启动队列并发送电子邮件,但首先我们需要一个电子邮件模板。将代码保存为test.blade.php,并保存在app/views/emails/目录中:

       <!DOCTYPE html>
       <html lang="en-US">
         <head>
           <meta charset="utf-8">
         </head>
         <body>
           <h2>Welcome to our newsletter</h2>
           <div>Hello {{$email}}, this is our test message fromour Awesome Laravel queue system.</div>
         /body>
       </html>

这是一个简单的电子邮件模板,将包装我们的电子邮件。

  1. 现在我们需要一个类来启动队列并发送电子邮件。将这些类文件直接保存到app文件夹中的routes.php文件中:
       //When the queue is pushed and waiting to be marshalled, we should assign a Class to make the job done 
       Class SendEmail {

         public function fire($job,$data) {

           //We first get the all data from our subscribers//database
           $subscribers = Subscribers::all(); 

           foreach ($subscribers as $each) {

             //Now we send an email to each subscriber
             Mail::send('emails.test',array('email'=>$each->email), function($message){

               $message->from('us@oursite.com', 'Our Name');

               $message->to($each->email);

             });
           }

           $job->delete();
         }
       }

我们在前面的代码中编写的SendEmail类将覆盖我们将分配的队列作业。fire()方法是 Laravel 自己的方法,用于处理队列事件。因此,当队列被管理时,fire()方法内的代码将运行。我们还可以在调用Queue::push()方法时将参数作为第二个参数传递给job

借助 Eloquent ORM,我们使用all()方法从数据库中获取了所有订阅者方法,然后使用foreach循环遍历了所有记录。

在成功处理job之后,底部使用delete()方法,以便下一次队列调用时不会再次启动job

在进一步深入代码之前,我们必须了解 Laravel 4 的新功能Email 类的基础知识。

使用 Email 类在队列内处理电子邮件

在进一步进行之前,我们需要确保我们的电子邮件凭据是正确的,并且我们已经正确设置了所有值。打开app/config/目录中的mail.php文件,并根据您的配置填写设置:

  • 参数驱动程序设置要使用的电子邮件驱动程序;mailsendmailsmtp是默认的邮件发送参数。

  • 如果您正在使用smtp,您需要根据您的提供商填写hostportencryptionusernamepassword字段。

  • 您还可以使用字段from设置默认的发件人地址,这样您就不必一遍又一遍地输入相同的地址。

  • 如果您正在使用sendmail作为邮件发送驱动程序,您应该确保参数sendmail中的路径是正确的。否则,邮件将无法发送。

  • 如果您仍在测试应用程序,或者您处于实时环境并希望测试更新而不会发送错误/未完成的电子邮件,您应该将pretend设置为true,这样它不会实际发送电子邮件,而是将它们保留在日志文件中供您调试。

当我们遍历所有记录时,我们使用了 Laravel 的新电子邮件发送器Mail类,它基于流行的组件Swiftmailer

Mail::send()方法有三个主要参数:

  • 第一个参数是电子邮件模板文件的路径,电子邮件将在其中包装

  • 第二个参数是将发送到视图的变量

  • 第三个参数是一个闭包函数,我们可以在其中设置标题fromtoCC/BCCattachments

此外,您还可以使用attach()方法向电子邮件添加附件

测试系统

设置队列系统和email类之后,我们准备测试我们编写的代码:

  1. 首先,确保数据库中有一些有效的电子邮件地址。

  2. 现在通过浏览器导航并输入your-site-url/queue/process

  3. 当您看到消息“队列已处理”时,这意味着队列已成功发送到我们的队列驱动程序。我想逐步描述这里发生的事情:

  • 首先,我们使用包含Queue::push()的队列驱动程序进行 ping,并传递我们需要排队的参数和附加数据

  • 然后,在队列驱动程序获取我们的响应后,它将向我们之前使用queue:subscribeartisan 命令设置的queue/push的 post 路由发出 post 请求

  • 当我们的脚本从队列驱动程序接收到push请求时,它将调度并触发排队事件

  • 触发后,类中的fire()方法将运行并执行我们分配给它的任务

  1. 过一段时间,如果一切顺利,您将开始在收件箱中收到这些电子邮件。

直接使用队列发送电子邮件

在某些发送电子邮件的情况下,特别是如果我们正在使用第三方 SMTP,并且正在发送用户注册、验证电子邮件等,队列调用可能不是最佳解决方案,但如果我们可以在发送电子邮件时直接将其排队,那将是很好的。Laravel 的Email类也可以处理这个问题。如果我们使用相同的参数使用Mail::queue()而不是Mail::send(),则电子邮件发送将借助队列驱动程序完成,并且最终用户的响应时间将更快。

总结

在本章中,我们使用 Laravel 的Form Builder类和 jQuery 的 Ajax 提交方法创建了一个简单的新闻订阅表单。我们对表单进行了验证和处理,并将数据保存到数据库中。我们还学习了如何使用 Laravel 4 的queue类轻松排队长时间的处理过程。我们还介绍了使用 Laravel 4 进行电子邮件发送的基础知识。

在下一章中,我们将编写一个问答网站,该网站将具有分页系统、标签系统、第三方身份验证库、问题和答案投票系统、选择最佳答案的选项以及问题的搜索系统。

第八章:构建问答 Web 应用程序

在本章中,我们将创建一个问答 Web 应用程序。首先,我们将学习如何从 Laravel 中移除 public 段,以便能够使用一些共享主机解决方案。然后,我们将使用第三方扩展进行认证和访问权限处理。最后,我们将创建一个问题系统,允许评论和回答问题,一个标签系统,点赞和踩,以及选择最佳答案。我们将使用中间表来处理问题标签。我们还将在各个地方受益于 jQuery Ajax 请求。以下是本章将涉及的主题:

  • 从 Laravel 4 中移除 public 段

  • 安装 Sentry 2 和一个认证库,并设置访问权限

  • 创建自定义过滤器

  • 创建我们的注册和登录表单

  • 创建我们的问题表和模型

  • 使用一个中间表创建我们的标签表

  • 创建和处理我们的问题表单

  • 创建我们的问题列表页面

  • 创建我们的问题页面

  • 创建我们的答案表和资源

  • 按标签搜索问题

从 Laravel 4 中移除 public 段

在一些现实情况下,你可能不得不坚持使用配置不良的共享 Web 主机解决方案,它们没有wwwpublic_html或类似的文件夹。在这种情况下,你会想要从你的 Laravel 4 安装中移除 public 段。要移除这个 public 段,有一些简单的步骤要遵循:

  1. 首先确保你有一个正在运行的 Laravel 4 实例。

  2. 然后,将public文件夹中的所有内容移动到父文件夹中(其中包括appbootstrapvendor和其他文件夹),然后删除空的 public 文件夹。

  3. 接下来,打开index.php文件(我们刚刚从 public 文件夹中移动过来),找到以下行:

require __DIR__.'/../bootstrap/autoload.php';

用以下行替换上一行:

require __DIR__.'/bootstrap/autoload.php';
  1. 现在,在index.php文件中找到这行:
$app = require_once __DIR__.'/../bootstrap/start.php';

用以下行替换上一行:

$app = require_once __DIR__.'/bootstrap/start.php';
  1. 现在,打开bootstrap文件夹下的paths.php文件,并找到这行:
'public' => __DIR__.'/../public',

用以下行替换上一行:

'public' => __DIR__.'/..',
  1. 如果你使用虚拟主机,请不要忘记更改目录设置并重新启动你的 Web 服务器。

在前面的步骤中,我们首先将所有内容从public文件夹移动到parent文件夹,因为我们将不再使用parent段。然后我们修改了index.php文件,以识别autoload.phpstart.php的正确路径,以便框架可以运行。如果一切顺利,当你刷新页面时不会看到任何问题,这意味着你已成功从 Laravel 4 安装中移除了 public 段。

注意

不要忘记,这种方法会使你的所有代码都可以在公共 Web 根目录中使用,这可能会给你的项目带来安全问题。在这种情况下,你应该避免使用这种方法,或者你应该找到一个更好的 Web 主机解决方案。

安装 Sentry 2 和一个认证库,并设置访问权限

在这一部分,我们将安装一个第三方库用于用户认证和访问权限,名为 Sentry 2,由Cartalyst提供。Cartalyst 是一个以开发者为中心的开源公司,专注于文档、社区支持和框架。在这一部分,我们将按照 Sentry 官方的 Laravel 4 安装步骤进行操作,还有一个简单的额外步骤,目前可以在docs.cartalyst.com/sentry-2/installation/laravel-4找到。

  1. 首先,打开你的composer.json文件,并在require属性中添加以下行:
"cartalyst/sentry": "2.0.*"
  1. 然后,运行 composer update 命令来获取包:
php composer.phar update
  1. 现在,打开app/config下的app.php文件,并在providers数组中添加以下行:
'Cartalyst\Sentry\SentryServiceProvider',
  1. 现在,在app.php中的aliases数组中添加以下行:
'Sentry' => 'Cartalyst\Sentry\Facades\Laravel\Sentry',
  1. 现在,运行以下命令来安装所需的表(或用户)到数据库中:
php artisan migrate --package=cartalyst/sentry
  1. 接下来,我们需要将 Sentry 2 的配置文件发布到我们的app文件夹中,这样我们就可以管理节流或其他设置(如果需要的话)。从终端运行以下命令:
php artisan config:publish cartalyst/sentry
  1. 现在,我们应该修改默认的用户模型,以便能够在 Sentry 2 中使用它。打开app/models目录下的User.php文件,并用以下代码替换所有内容:
<?php
class User extends Cartalyst\Sentry\Users\Eloquent\User {
}
  1. 最后,我们应该创建我们的管理员用户。将以下代码添加到app文件夹下的routes.php文件中,并运行一次。之后注释或删除该代码。我们实际上为我们的系统分配了 ID=1 的管理员,具有名为admin的访问权限。
/**
* This method is to create an admin once.
* Just run it once, and then remove or comment it out.
**/
Route::get('create_user',function(){

$user = Sentry::getUserProvider()->create(array(
  'email' => 'admin@admin.com',
  //password will be hashed upon creation by Sentry 2
  'password' => 'password',
  'first_name' => 'John',
  'last_name' => 'Doe',
  'activated' => 1,
  'permissions' => array (
    'admin' => 1
  )
));
return 'admin created with id of '.$user->id;
});

通过这样做,您已成功创建了一个以admin@admin.com作为电子邮件地址和password作为密码的用户。密码将在 Sentry 2 创建时自动进行哈希处理,因此我们无需在创建之前对密码进行哈希和盐处理。我们将管理员的名字设置为John,姓氏设置为Doe。此外,我们为刚刚生成的用户设置了一个名为admin的权限,以在请求处理之前检查访问权限。

您现在已经准备就绪。如果一切顺利,并且您检查您的数据库,您应该会看到由 Laravel 4 生成的迁移表(在 Laravel 3 中您必须在第一次迁移之前手动设置),以及由 Sentry 2 生成的表。在users表中,您应该会看到我们的闭包方法生成的用户条目。

现在我们的用户认证系统已经准备就绪,我们需要生成我们的过滤器,然后创建注册和登录表单。

创建自定义过滤器

自定义过滤器将帮助我们过滤请求,并在请求之前进行一些预检查。利用 Sentry 2 内置的方法,我们可以轻松定义自定义过滤器。但首先,我们需要定义一些在项目中将要使用的路由。

将以下代码添加到app文件夹下的routes.php文件中:

//Auth Resource
Route::get('signup',array('as'=>'signup_form', 'before'=>
'is_guest', 'uses'=>'AuthController@getSignup'));
Route::post('signup',array('as'=>'signup_form_post', 'before' =>
'csrf|is_guest', 'uses' => 'AuthController@postSignup'));
Route::post('login',array('as'=>'login_post', 'before' =>
'csrf| is_guest', 'uses' => 'AuthController@postLogin'));
Route::get('logout',array('as'=>'logout', 'before'=>'
user', 'uses' => 'AuthController@getLogout'));
//---- Q & A Resources
Route::get('/',array('as'=>'index','uses'=>
'MainController@getIndex'));

在这些命名资源中,名称是在数组中用键as定义的,过滤器是用键before设置的。正如您所看到的,有一些before参数,比如is_guestuser。这些过滤器将在用户发出任何请求之前运行,甚至调用控制器。键uses设置了在调用资源时将执行的控制器。我们稍后将为这些控制器编写代码。因此,例如,用户甚至无法尝试提交登录表单。如果用户尝试这样做,我们的过滤器将在用户发出请求之前运行并进行过滤。

现在我们的路由已经准备就绪,我们可以添加过滤器。要添加过滤器,请打开app文件夹下的filters.php文件,并添加以下代码:

/*
 |----------------------------------------------------------- 
 | Q&A Custom Filters
 |-----------------------------------------------------------
*/

Route::filter('user',function($route,$request){
  if(Sentry::check()) {
    //is logged in
  } else {
    return Redirect::route('index')
      ->with('error','You need to log in first');
  }
});

Route::filter('is_guest',function($route,$request){
  if(!Sentry::check()) {
    //is a guest
  } else {
    return Redirect::route('index')
      ->with('error','You are already logged in');
  }
});

Route::filter('access_check',function($route,$request,$right){
  if(Sentry::check()) {
    if(Sentry::getUser()->hasAccess($right)) {
      //logged in and can access
    } else {
      return Redirect::route('index')
        ->with('error','You don\'t have enough priviliges to access that page');
    }
  } else {
    return Redirect::route('index')
      ->with('error','You need to log in first');
  }
});

Route::filter()方法允许我们创建自己的过滤器。第一个参数是过滤器的名称,第二个参数是一个闭包函数,它本身至少需要两个参数。如果需要向过滤器提供参数,可以将其添加为第三个参数。

Sentry 2 的check()辅助函数返回一个布尔值,用于判断用户是否已登录。如果返回 true,表示用户已登录,否则正在浏览网页的用户尚未登录。在我们的自定义过滤器useris_guest中,我们正是在检查这一点。您的过滤器的通过条件可以留空。但如果用户未满足过滤器的条件,可以采取适当的行动。在我们的示例中,我们将用户重定向到我们的index路由。

然而,我们的第三个过滤器access_check有点复杂。正如你所看到的,我们添加了一个名为$right的第三个参数,我们将通过调用过滤器传递它。这个过滤器检查两个条件。首先,它使用Sentry::check()方法检查用户是否已登录。然后,它使用hasAccess()方法检查用户是否有访问$right部分的权限(我们将在定义过滤器时看到)。但是这个方法首先需要一个当前登录的用户。为此,我们将使用 Sentry 2 的getUser()方法验证当前用户的信息。

在调用过滤器时传递参数,可以使用filter_name:parameter1, parameter2。在我们的示例中,我们将使用过滤器access_check:admin来检查用户是否是管理员。

before参数中使用多个过滤器,可以在参数之间添加|字符。在我们的示例中,我们的登录提交和注册资源的过滤器被定义为csrf|guest(csrf 在 Laravel 的filters.php文件中是预定义的)。

创建我们的注册和登录表单

在创建我们的注册和登录表单之前,我们需要一个模板来设置这些部分。我将使用我为本章生成的自定义 HTML/CSS 模板,这个模板受到开源问答脚本Question2AnswerSnow主题的启发。

我们执行以下步骤来创建我们的注册和登录表单:

  1. 首先,将提供的示例代码中assets文件夹中的所有内容复制到项目文件夹的根目录(appbootstrap和其他文件夹所在的位置),因为我们在本章的第一节中删除了 public 文件夹部分。

  2. 接下来,在app/views下的template_masterpage.blade.php文件中添加以下代码:

<!DOCTYPE html>
<!--[if lt IE 7]> <html class="no-js lt-ie9 lt-ie8 lt-ie7">
<![endif]-->
<!--[if IE 7]> <html class="no-js lt-ie9 lt-ie8">
<![endif]-->
<!--[if IE 8]> <html class="no-js lt-ie9">
<![endif]-->
<!--[if gt IE 8]><!--> <html class="no-js">
<!--<![endif]-->

<head>
  <meta charset="utf-8" />
  <title>{{isset($title)?$title.' | ':''}} LARAVEL Q & A
  </title>
  {{ HTML::style('assets/css/style.css') }}
</head>
<body>

  {{-- We include the top menu view here --}}
  @include('template.topmenu')

  <div class="centerfix" id="header">
  <div class="centercontent">
    <a href="{{URL::route('index')}}">
      {{HTML::image('assets/img/header/logo.png')}}
    </a>
  </div>
  </div>
  <div class="centerfix" id="main" role="main">
  <div class="centercontent clearfix">
    <div id="contentblock">

    {{-- Showing the Error and Success Messages--}}
    @if(Session::has('error'))
    <div class="warningx wredy">
      {{Session::get('error')}}
    </div>
    @endif

    @if(Session::has('success'))
    <div class="warningx wgreeny">
      {{Session::get('success')}}
    </div>
    @endif

    {{-- Content section of the template --}}
    @yield('content')
    </div>
  </div>
  </div>
  {{-- JavaScript Files --}}
  {{ HTML::script('assets/js/libs.js') }}
  {{ HTML::script('assets/js/plugins.js') }}
  {{ HTML::script('assets/js/script.js') }}

  {{-- Each page's custom assets (if available) will be yielded here --}}
  @yield('footer_assets')

</body>
</html>

现在,让我们来看代码:

  • 如果我们使用title属性加载视图,<title>标签将包含标题;否则它将只显示我们网站的名称。

  • HTML类的style()方法将帮助我们轻松地向我们的模板添加 CSS 文件。此外,HTML类的script()方法允许我们向输出的 HTML 文件添加 JavaScript。

  • 我们使用 Blade 模板引擎的@include()方法将另一个文件包含到我们的template_masterpage.blade.php文件中。我们将在下一步中描述它的部分。

  • URL类的route()方法将返回一个命名路由的链接。这实际上非常方便,因为如果我们更改 URL 结构,我们不需要深入所有模板文件并编辑所有链接。

  • HTML类的image()方法允许我们向我们的模板添加<img>标签。

  • 在过滤器中,我们使用with()方法和参数error重定向到路由页面。如果我们使用with()加载页面(View::make()),参数将是变量。但是因为我们已经将用户重定向到一个页面,通过with()传递的这些参数将是会话 flashdata,只能使用一次。为了检查这些会话是否设置,我们使用Session类的has()方法。Session::has('sessionName')将返回一个布尔值,以确定会话是否设置。如果设置了,我们可以使用Session类的get()方法在视图、控制器和其他地方使用它。

  • Blade 模板引擎的@yield()方法获取@section()中的数据,并将其解析到主模板页面。

  1. 在上一节中,我们通过调用@include()方法包含了另一个视图,如@include('template.topmenu')。现在将以下代码保存为topmenu.blade.php,放在app/views/template下:
{{-- Top error (about login etc.) --}}
@if(Session::has('topError'))
  <div class="centerfix" id="infobar">
    <div class="centercontent">{{ Session::get('topError') }}
    </div>
  </div>
@endif

{{-- Check if a user is logged in, login and logout has different templates --}}
@if(!Sentry::check())
<div class="centerfix" id="login">
  <div class="centercontent">
    {{Form::open(array('route'=>'login_post'))}}
    {{Form::email('email', Input::old('email'), array('placeholder'=>'E-mail Address'))}}
    {{Form::password('password', array('placeholder' => 'Password'))}}
    {{Form::submit('Log in!')}}
    {{Form::close()}}

    {{HTML::link('signup_form','Register',array(),array('class'=>'wybutton'))}}
  </div>
</div>
@else
  <div class="centerfix" id="login">
    <div class="centercontent">
      <div id="userblock">Hello again, {{HTML::link('#',Sentry::getUser()->first_name.' '.Sentry::getUser()->last_name)}}</div>
      {{HTML::linkRoute('logout','Logout',array(),array('class'=>'wybutton'))}}
    </div>
  </div>
@endif

现在,让我们来看代码:

  • 在我们的模板中,有两个错误消息,其中第一个完全保留给将在顶部显示的登录区域。我将其命名为error_top。使用我们刚学到的has()get()方法,我们检查是否存在错误,并显示它。

  • 顶部菜单将取决于用户是否已登录。因此,我们使用 Sentry 2 的用户检查方法check()创建一个if子句来检查用户是否已登录。如果用户未登录(访客),我们将显示使用Form类制作的登录表单,否则我们将显示用户infobar,其中包含个人资料和注销按钮。

  1. 现在,我们需要一个注册表单页面。我们之前已经在app文件夹下的routes.php文件中定义了它的方法:
//Auth Resource
Route::get('signup',array('as'=>'signup_form', 'before' => 'is_guest', 'uses' => 'AuthController@getSignup'));
Route::post('signup',array('as' => 'signup_form_post', 'before' => 'csrf|is_guest', 'uses' => 'AuthController@postSignup'));
  1. 根据我们创建的路由资源,我们需要一个名为AuthController的控制器,其中包含两个名为getSignup()postSignup()的方法。现在让我们首先创建控制器。打开你的终端并输入以下命令:
**php artisan controller:make AuthController**

  1. 上一个命令将在app/controllers文件夹下创建一个名为AuthController.php的新文件,并带有一些默认方法。删除AuthController类内的现有代码,并添加以下代码到该类内,以创建注册表单:
/**
  * Signup GET method
**/
public function getSignup() {
  return View::make('qa.signup')
    ->with('title','Sign Up!');
}
  1. 现在我们需要一个视图文件来制作表单。将以下代码保存为signup.blade.php,放在app/views/qa文件夹下:
@extends('template_masterpage')

@section('content')
  <h1 id="replyh">Sign Up</h1>
  <p class="bluey">Please fill all the credentials correctly to register to our site</p>
  {{Form::open(array('route'=>'signup_form_post'))}}
    <p class="minihead">First Name:</p>
    {{Form::text('first_name',Input::get('first_name'),array('class'=>'fullinput'))}}
    <p class="minihead">Last Name:</p>
    {{Form::text('last_name',Input::get('last_name'),array('class'=>'fullinput'))}}<p class="minihead">E-mail address:</p>
    {{Form::email('email',Input::get('email'),array('class'=>'fullinput'))}}
    <p class="minihead">Password:</p>
    {{Form::password('password','',array('class'=>'fullinput'))}}
    <p class="minihead">Re-password:</p>
    {{Form::password('re_password','',array('class'=>'fullinput'))}}
    <p class="minihead">Your personal info will not be shared with any 3rd party companies.</p>
    {{Form::submit('Register now!')}}
  {{Form::close()}}
@stop

如果你已经正确完成了所有步骤,当你导航到chapter8.dev/signup时,你应该会看到以下表单:

创建我们的注册和登录表单

验证和处理表单

现在,我们需要验证和处理表单。我们首先需要定义我们的验证规则。将以下代码添加到app/models文件夹下的user.php文件中的User类中:

public static $signup_rules = array(
  'first_name' => 'required|min:2',
  'last_name' => 'required|min:2',
  'email' => 'required|email|unique:users,email',
  'password' => 'required|min:6',
  're_password' => 'required|same:password'
);

前面代码中提到的规则将使所有字段都为required。我们将first_namelast_name列设置为required,并设置最小长度为两个字符。我们将email字段设置为有效的电子邮件格式,并且代码将检查users表(在安装 Sentry 2 时创建)中的唯一电子邮件地址。我们将password字段设置为required,并且其长度应至少为六个字符。我们还将re_password字段设置为与password字段匹配,以确保密码输入正确。

注意

Sentry 2 也可以在尝试登录用户时抛出唯一电子邮件检查异常。

在处理表单之前,我们需要一个虚拟的索引页面来在成功注册后返回用户。我们将通过以下步骤创建一个临时的索引页面:

  1. 首先,运行以下命令来创建一个新的控制器:
**php artisan controller:make MainController**

  1. 然后,删除所有自动插入的方法,并在类内添加以下方法:
public function getIndex() {
  return View::make('qa.index');
}
  1. 现在,将此视图文件保存为index.blade.php,放在app/views/qa文件夹下:
@extends('template_masterpage')

@section('content')
Heya!
@stop
  1. 现在,我们需要一个控制器方法(我们在routes.php中定义的)来处理signup表单的post请求。为此,将以下代码添加到app/controllers文件夹下的AuthController.php文件中:
/**
  * Signup Post Method
**/
public function postSignup() {

  //Let's validate the form first
  $validation = Validator::make(Input::all(),User::$signup_rules);

  //let's check if the validation passed
  if($validation->passes()) {

    //Now let's create the user with Sentry 2's create method
    $user = Sentry::getUserProvider()->create(array(
      'email' => Input::get('email'),
      'password' => Input::get('password'),
      'first_name' => Input::get('first_name'),
      'last_name' => Input::get('last_name'),
      'activated' => 1
    ));

    //Since we don't use an email validation in this example, let's log the user in directly
    $login = Sentry::authenticate(array('email'=>Input::get('email'),'password'=>Input::get('password')));

    return Redirect::route('index')
      ->with('success','You\'ve signed up and logged in successfully!');
    //if the validation failed, let's return the user 
    //to the signup form with the first error message
  } else {
    return Redirect::route('signup_form')
    ->withInput(Input::except('password','re_password'))
      ->with('error',$validation->errors()->first());
  }
}

现在,让我们来看看代码:

  1. 首先,我们使用 Laravel 内置的表单验证类来检查表单项,使用我们在模型中定义的规则。

  2. 我们使用passes()方法来检查表单验证是否通过。我们也可以使用fails()方法来检查相反的情况。

如果验证失败,我们将使用withInput()将用户返回到注册表单,并使用Input::except()过滤一些列,如passwordre_password,以便这些字段的值不会返回。此外,通过使用with传递参数,将返回表单验证的错误消息。$validation->errors()->first()在表单验证步骤后返回第一个错误消息字符串。

验证和处理表单

如果验证通过,我们将使用提供的凭据创建一个新用户。我们将activated列设置为1,这样在我们的示例中注册过程不需要电子邮件验证。

注意

Sentry 2 还使用 try/catch 子句来捕获错误。不要忘记查看 Sentry 2 的文档,了解如何捕获异常错误。

  1. 由于我们没有使用电子邮件验证系统,我们可以简单地使用 Sentry 2 的authenticate()方法对用户进行身份验证和登录,就在注册后。第一个参数接受一个emailpassword的数组(与key => value匹配),可选的第二个参数接受一个布尔值作为输入,以检查用户是否要被记住(记住我按钮)。

  2. 身份验证后,我们只需将用户重定向到我们的index路由,并显示成功消息,如下图所示:

处理登录和注销请求

现在我们的注册系统已经准备好了,我们需要处理登录和注销请求。由于我们的登录表单已经准备好了,我们可以直接进行处理。要处理登录和注销请求,我们执行以下步骤:

  1. 首先,我们需要登录表单验证规则。将以下代码添加到app/models目录下的User.php文件中:
public static $login_rules = array(
	'email'		=> 'required|email|exists:users,email',
	'password'	=> 'required|min:6'
);
  1. 现在,我们需要一个控制器方法来处理登录请求。在app/controllers目录下的AuthController.php文件中添加以下代码:
/**
 * Login Post Method Resource
**/
public function postLogin() {
  //let's first validate the form:
  $validation = Validator::make(Input::all(),User::$login_rules);

  //if the validation fails, return to the index page with first error message
  if($validation->fails()) {
    return Redirect::route('index')
      ->withInput(Input::except('password'))
      ->with('topError',$validation->errors()->first());
  } else {

    //if everything looks okay, we try to authenticate the user
    try {

      // Set login credentials
      $credentials = array('email' => Input::get('email'),'password' => Input::get('password'),);

      // Try to authenticate the user, remember me is set to false
      $user = Sentry::authenticate($credentials, false);
      //if everything went okay, we redirect to index route with success message
      return Redirect::route('index')
        ->with('success','You\'ve successfully logged in!');
    } catch (Cartalyst\Sentry\Users\LoginRequiredException $e) {
      return Redirect::route('index')
        -> withInput(Input::except('password'))
        ->with('topError','Login field is required.');
    } catch (Cartalyst\Sentry\Users\PasswordRequiredException $e) {
      return Redirect::route('index')
        -> withInput(Input::except('password'))
        ->with('topError','Password field is required.');
    } catch (Cartalyst\Sentry\Users\WrongPasswordException $e) {
      return Redirect::route('index')
        -> withInput(Input::except('password'))
        ->with('topError','Wrong password, try again.');
    } catch (Cartalyst\Sentry\Users\UserNotFoundException $e) {
      return Redirect::route('index')
        -> withInput(Input::except('password'))
        ->with('topError','User was not found.');
    } catch (Cartalyst\Sentry\Users\UserNotActivatedException $e) {
      return Redirect::route('index')
        -> withInput(Input::except('password'))
        ->with('topError','User is not activated.');
    }

    // The following is only required if throttle is enabled
    catch (Cartalyst\Sentry\Throttling\UserSuspendedException $e) {
    return Redirect::route('index')
      -> withInput(Input::except('password'))
      ->with('topError','User is suspended.');
    } catch (Cartalyst\Sentry\Throttling\UserBannedException $e) {
      return Redirect::route('index')
        -> withInput(Input::except('password'))
        ->with('topError','User is banned.');
    }
  }
}

现在,让我们来看看代码:

  1. 首先,我们使用 Laravel 内置的表单验证类检查表单项,使用我们在模型中定义的规则。

  2. 然后,我们使用表单验证类的fails()方法检查表单验证是否失败。如果表单验证失败,我们将用户返回到index路由,并显示第一个表单验证错误。

  3. 上面代码中的else子句包含了如果表单验证通过将要执行的事件。在这里,我们使用 Sentry 2 的 try/catch 子句对用户进行身份验证,捕获所有的异常,并根据异常的类型返回错误消息。

在我们的示例应用程序中,我们不需要所有的异常,但是作为一个示例,我们尝试展示所有的异常,以防您需要在跟进时做一些不同的事情。

注意

所有这些 try/catch 异常都在 Sentry 2 的网站上有记录。

  1. 如果 Sentry 2 没有抛出任何异常,我们将返回到带有成功消息的索引页面。

  2. 现在,关于身份验证,唯一剩下的事情就是注销按钮。要创建一个,将以下代码添加到app/controllers目录下的AuthController.php文件中:

/**
  * Logout method 
**/
public function getLogout() {
  //we simply log out the user
  Sentry::logout();

  //then, we return to the index route with a success message
  return Redirect::route('index')
    ->with('success','You\'ve successfully signed out');
}

现在让我们来看看代码:

  1. 首先,我们调用 Sentry 2 的logout()方法,将用户注销。

  2. 然后,我们只需将当前是访客的用户重定向到index路由,并显示成功消息,告诉他们已成功注销。

现在我们的身份验证系统已经准备好了,我们准备创建我们的问题表。

创建我们的问题表和模型

现在我们有一个完全可用的身份验证系统,我们准备创建我们的questions表。为了创建我们的questions表,我们将使用数据库迁移。

要创建一个迁移,请在终端中运行以下命令:

**php artisan migrate:make create_questions_table --table= questions --create**

上面的命令将在app/database/migrations下创建一个新的迁移。

对于问题,我们将需要一个问题标题,问题详情,问题的提问者,问题的日期,问题被查看的次数,投票的总和以及问题的标签。

现在,打开您刚刚创建的迁移,并用以下代码替换其内容:

Schema::create('questions', function(Blueprint $table)
{
  //Question's ID
  $table->increments('id');
  //title of the question
  $table->string('title',400)->default('');
  //asker's id
  $table->integer('userID')->unsigned()->default(0);
  //question's details
  $table->text('question')->default('');
  //how many times it's been viewed:
  $table->integer('viewed')->unsigned()->default(0);
  //total number of votes:
  $table->integer('votes')->default(0);
  //Foreign key to match userID (asker's id) to users
  $table->foreign('userID')->references('id')->on('users')->onDelete('cascade');
  //we will get asking time from the created_at column
  $table->timestamps();
});

对于标签,我们将使用一个数据透视表,这就是为什么它们不在我们当前的模式中。对于投票,在这个例子中,我们只是持有一个整数(可以是正数或负数)。在现实世界的应用中,您会想要使用第二个数据透视表来保留用户的投票,以防止重复投票,并获得更准确的结果。

  1. 现在您的模式已经准备好了,请使用以下命令运行迁移:
**php artisan migrate**

  1. 成功迁移模式后,我们现在需要一个模型来从 Eloquent 中受益。将以下代码保存为Question.php,放在app/models目录下:
<?php

class Question extends Eloquent {

  protected $fillable = array('title', 'userID', 'question', 'viewed', 'answered', 'votes');

}
  1. 现在,我们需要数据库关系来匹配表。首先,将以下代码添加到app/models文件夹下的User.php文件中:
public function questions() {
  return $this->hasMany('Question','userID');
}
  1. 接下来,将以下代码添加到app/models文件夹下的Question.php文件中:
public function users() {
  return $this->belongsTo('User','userID');
}

由于用户可能有多个问题,我们在我们的User模型中使用了hasMany()方法来进行关联。同样,由于所有的问题都是用户拥有的,我们使用belongsTo()方法来将问题与用户匹配。在这些方法中,第一个参数是模型名称,在我们的例子中是QuestionUser。第二个参数是该模型中用来匹配表的列名,在我们的例子中是userID

创建我们的标签表和枢轴表

首先,我们应该理解为什么我们需要标签的枢轴表。在现实世界的情况下,一个问题可能有多个标签;同样,一个标签可能有多个问题。在这种情况下(多对多关系),两个表都可能有多个相互匹配的情况,我们应该创建并使用第三个枢轴表。

  1. 首先,我们应该使用架构创建一个新的标签表。打开您的终端并运行以下命令来创建我们的枢轴表架构:
**php artisan migrate:make create_tags_table --table= tags --create**

  1. 现在我们需要填充表的内容。在我们的例子中,我们只需要标签名称和标签的友好 URL 名称。用以下代码替换架构的up函数内容:
Schema::create('tags', function(Blueprint $table)
{
  //id is needed to match pivot
  $table->increments('id');

  //Tag's name
  $table->string('tag')->default('');
  //Tag's URL-friendly name
  $table->string('tagFriendly')->unique();

  //I like to keep timestamps
  $table->timestamps();
});

我们有id列来匹配问题和枢轴表中的标签。我们有一个字符串字段tag,它将是标签的标题,列tagFriendly是将显示为 URL 的内容。我还保留了时间戳,这样,将来它可以为我们提供标签创建的信息。

  1. 最后,在您的终端中运行以下命令来运行迁移并安装表:
**php artisan migrate**

  1. 现在,我们需要一个tags表的模型。将以下文件保存为Tag.php,放在app/models文件夹下:
<?php

class Tag extends Eloquent {

  protected $fillable = array('tag', 'tagFriendly');

}
  1. 现在,我们需要创建我们的枢轴表。作为一个良好的实践,它的名称应该是modelname1_modelname2,并且内容按字母顺序排序。在我们的例子中,我们有questionstags表,所以我们将枢轴表的名称设置为question_tags(这不是强制的,您可以给您的枢轴表任何名称)。正如您可能猜到的那样,它的架构将有两列来匹配两个表和这两个列的外键。您甚至可以向枢轴表添加额外的列。

要创建迁移文件,请在终端中运行以下命令:

**php artisan migrate:make create_question_tags_table --table=question_tags --create**

  1. 现在,打开我们在app/databasemigrations文件夹中生成的架构,并用以下代码修改其up()方法的内容:
Schema::create('question_tags', function(Blueprint $table)
{
  $table->increments('id');

  $table->integer('question_id')->unsigned()->default(0);
  $table->integer('tag_id')->unsigned()->default(0);

  $table->foreign('question_id')->references('id')->on('questions')->onDelete('cascade');
  $table->foreign('tag_id')->references('id')->on('tags')->onDelete('cascade');

  $table->timestamps();
});

我们需要两列,其名称结构应为modelname_id。在我们的迁移中,它们是question_idtag_id。此外,我们已经设置了外键来匹配它们在我们的数据库中。

  1. 现在,运行迁移并安装表:
**php artisan migrate**

  1. 现在,我们需要添加方法来描述 Eloquent 我们正在使用一个枢轴表。将以下代码添加到app/models文件夹下的Question.php文件中:
public function tags() {
  return $this->belongsToMany('Tag','question_tags')->withTimestamps();
}

描述枢轴信息到标签模型,将以下代码添加到app/models文件夹下的Tag.php文件中:

public function questions() {
  return $this->belongsToMany('Question','question_tags')->withTimestamps();
}

belongsToMany()方法中的第一个参数是模型名称,第二个参数是枢轴表的名称。使用withTimestamps()(它为我们带来了枢轴数据的创建和更新日期)是可选的。此外,如果我们有一些额外的数据要添加到枢轴表中,我们可以使用withPivot()方法来调用它。考虑以下示例代码:

$this->belongsToMany('Question ', 'question_tags')->withPivot('column1', 'column2')->withTimestamps();

现在我们的枢轴表结构准备好了,在后面的章节中,我们可以轻松地获取问题的标签和所有标记为$tagname的问题。

创建和处理我们的问题表单

现在我们的结构准备好了,我们可以继续创建和处理我们的问题表单。

创建我们的问题表单

我们执行以下步骤来创建我们的问题表单:

  1. 首先,我们需要为问题表单创建一个新的路由资源。打开app文件夹中的routes.php文件,并添加以下代码:
Route::get('ask',array('as'=>'ask', 'before'=>'user', 
   'uses' => 'QuestionsController@getNew'));

Route::post('ask',array('as'=>'ask_post', 
  'before'=>'user|csrf', 'uses' => 
  'QuestionsController@postNew'));
  1. 现在我们的资源已经定义,我们需要将资源添加到顶部菜单以进行导航。打开app/views/template目录下的topmenu.blade.php文件,并找到以下行:
{{HTML::linkRoute('logout','Logout',array(), array('class'=>'wybutton'))}}

在以下行的上方添加上述行:

{{HTML::linkRoute('ask','Ask a Question!', array(), array('class'=>'wybutton'))}}
  1. 现在,我们需要控制器文件来处理资源。在您的终端中运行以下命令:
**php artisan controller:make QuestionsController**

  1. 接下来,打开app/controllers目录下新创建的QuestionsController.php文件,并删除类中的所有方法。然后添加以下代码:
/**
  * A new question asking form
**/
public function getNew() {
  return View::make('qa.ask')
    ->with('title','New Question');
}
  1. 现在,我们需要创建我们刚刚分配的视图。将以下代码保存为ask.blade.php,放在app/views/qa目录下:
@extends('template_masterpage')

@section('content')

  <h1 id="replyh">Ask A Question</h1>
  <p class="bluey">Note: If you think your question's been answered correctly, please don't forget to click "✓" icon to mark the answer as "correct".</p>
  {{Form::open(array('route'=>'ask_post'))}}

  <p class="minihead">Question's title:</p>
  {{Form::text('title',Input::old('title'),array('class'=>'fullinput'))}}

  <p class="minihead">Explain your question:</p>
  {{Form::textarea('question',Input::old('question'),array('class'=>'fullinput'))}}

  <p class="minihead">Tags: Use commas to split tags (tag1, tag2 etc.). To join multiple words in a tag, use - between the words (tag-name, tag-name-2):</p>
  {{Form::text('tags',Input::old('tags'),array('class'=>'fullinput'))}}
  {{Form::submit('Ask this Question')}}
  {{Form::close()}}

@stop
@section('footer_assets')

  {{-- A simple jQuery code to lowercase all tags before submission --}}
  <script type="text/javascript">
    $('input[name="tags"]').keyup(function(){
      $(this).val($(this).val().toLowerCase());
    });
  </script>

@stop

除了我们之前创建的视图之外,在这个视图中,我们通过填充footer_assets部分向页脚添加了 JavaScript 代码,这是我们在主页面中之前定义的。

  1. 如果您已经正确完成了所有操作,当您导航到site.com/ask时,您将看到一个类似以下截图的样式化表单:创建我们的问题表单

现在我们的问题表单已经准备好,我们可以开始处理表单了。

处理我们的问题表单

为了处理表单,我们需要一些验证规则和控制器方法。

  1. 首先,将以下表单验证规则添加到app/models目录下的Question.php文件中:
public static $add_rules = array('title' => 'required|min:2','question' => 'required|min:10');
  1. 成功保存问题后,我们希望向用户提供问题的永久链接,以便用户可以轻松访问问题。但是,为了做到这一点,我们首先需要定义一个创建此链接的路由。将以下行添加到app文件夹中的routes.php文件中:
Route::get('question/{id}/{title}',array('as'=> 'question_details', 'uses' => 'QuestionsController@getDetails' ))-> where(array('id'=>'[0-9]+' , 'title' => '[0-9a-zA-Z\-\_]+'));

我们将两个参数设置到这个路由中,idtitleid参数必须是正整数,而title应该只包含字母数字字符、分数和下划线。

  1. 现在,我们准备处理问题表单。将以下代码添加到app/controllers目录下的QuestionsController.php文件中:
/**
 * Post method to process the form
**/
public function postNew() {

  //first, let's validate the form
  $validation = Validator::make(Input::all(), Question::$add_rules);

  if($validation->passes()) {
    //First, let's create the question
    $create = Question::create(array('userID' => Sentry::getUser()->id,'title' => Input::get('title'),'question' => Input::get('question')
    ));

    //We get the insert id of the question
    $insert_id = $create->id;

    //Now, we need to re-find the question to "attach" the tag to the question
    $question = Question::find($insert_id);

    //Now, we should check if tags column is filled, and split the string and add a new tag and a relation
    if(Str::length(Input::get('tags'))) {
      //let's explode all tags from the comma
      $tags_array = explode(',', Input::get('tags'));
      //if there are any tags, we will check if they are new, if so, we will add them to database
      //After checking the tags, we will have to "attach" tag(s) to the new question 
      if(count($tags_array)) {
        foreach ($tags_array as $tag) {
          //first, let's trim and get rid of the extra space bars between commas 
          //(tag1, tag2, vs tag1,tag2) 
          $tag = trim($tag);

          //We should double check its length, because the user may have just typed "tag1,,tag2" (two or more commas) accidentally
          //We check the slugged version of the tag, because tag string may only be meaningless character(s), like "tag1,+++//,tag2"
          if(Str::length(Str::slug($tag))) {
            //the URL-Friendly version of the tag
            $tag_friendly = Str::slug($tag);

            //Now let's check if there is a tag with the url friendly version of the provided tag already in our database:
            $tag_check = Tag::where('tagFriendly',$tag_friendly);

            //if the tag is a new tag, then we will create a new one
            if($tag_check->count() == 0) {
              $tag_info = Tag::create(array('tag' => $tag,'tagFriendly' => $tag_friendly));

              //If the tag is not new, this means There was a tag previously added on the same name to another question previously
              //We still need to get that tag's info from our database 
            } else {
              $tag_info = $tag_check->first();
            }
          }

          //Now the attaching the current tag to the question
          $question->tags()->attach($tag_info->id);
        }
      }
    }

    //lastly, we should return the user to the asking page with a permalink of the question
    return Redirect::route('ask')
      ->with('success','Your question has been created successfully! '.HTML::linkRoute('question_details','Click here to see your question',array('id'=>$insert_id,'title'=>Str::slug($question->title))));

  } else {
    return Redirect::route('ask')
      ->withInput()
      ->with('error',$validation->errors()->first());
  }
}

现在,让我们来看看代码:

  1. 首先,我们运行表单验证类来检查数值是否有效。如果验证失败,我们将用户带回问题页面,并显示用户之前提供的旧输入以及第一个验证错误消息。

  2. 如果验证通过,我们继续处理表单。我们首先创建并添加问题,向数据库添加一行,然后获取我们刚刚创建的行。为了获取当前用户的 ID,我们使用 Sentry 2 的getUser()方法的id对象,该方法返回当前登录用户的信息。

  3. 创建问题后,我们检查tags字段的长度。如果字段不为空,我们将字符串在逗号处分割,并创建一个原始的tags数组。

  4. 之后,我们循环遍历我们分割的每个标签,并使用 Laravel 4 的String类的slug()方法创建它们的友好 URL 版本。如果生成的版本长度大于 0,则是有效的标签。

  5. 在找到所有有效的标签之后,我们检查数据库是否已经创建了标签。如果是,我们获取它的 ID。如果标签是系统中的新标签,那么我们就创建一个新的标签。这样,我们就避免了系统中不必要的多个标签。

  6. 之后,我们使用attach()方法在中间表中创建一个新的标签关系。要附加一个新的关系,我们首先需要找到要附加的 ID,然后转到附加的模型并使用attach()方法。

  7. 在我们的示例中,我们需要将问题附加到标签上。因此,我们找到需要附加的问题,使用多对多关系来显示标签将附加到问题,并将标签的id附加到问题上。

  8. 如果一切顺利,您应该会被重定向回问题页面,并显示一个成功消息和问题的永久链接。

  9. 另外,如果您检查question_tags表,您会看到填充的关系数据。

注意

始终验证和过滤来自表单的内容,并确保你不接受任何不需要的内容。

成功添加问题后,你应该会看到一个如下截图的页面:

处理我们的问题表单

创建我们的问题列表页面

现在我们可以创建问题了,是时候用实际的问题数据填充我们的虚拟索引页面了。为此,打开app/controllers下的MainController.php文件,并用以下代码修改getIndex()函数:

public function getIndex() {
  return View::make('qa.index')
    ->with('title','Hot Questions!')
    ->with('questions',Question::with('users','tags')->orderBy('id','desc')->paginate(2));
}

在这个方法中,我们加载了相同的页面,但添加了两个名为titlequestions的变量。title变量是我们应用程序的动态标题,questions变量保存了最后两个问题,带有分页。如果使用paginate($number)而不是get(),你可以获得一个准备就绪的分页系统。此外,使用with()方法,我们直接预加载了userstags关系,以获得更好的性能。

在视图中,我们将为问题提供一个简单的点赞/踩选项,以及一个标记为$tag的问题的路由链接。为此,我们需要一些新的路由。将以下代码添加到app文件夹下的routes.php文件中:

//Upvoting and Downvoting
Route::get('question/vote/{direction}/{id}',array('as'=> 'vote', 'before'=>'user', 'uses'=> 'QuestionsController@getvote'))->where (array('direction'=>'(up|down)', 'id'=>'[0-9]+'));

//Question tags page
Route::get('question/tagged/{tag}',array('as'=>'tagged','uses'=>'QuestionsController@getTaggedWith'))->where('tag','[0-9a-zA-Z\-\_]+');

现在打开app/views/qa下的index.blade.php文件,并用以下代码修改整个文件:

@extends('template_masterpage')

@section('content')
  <h1>{{$title}}</h1>

  @if(count($questions))

    @foreach($questions as $question)

      <?php
        //Question's asker and tags info
        $asker = $question->users;
        $tags = $question->tags;	 
      ?>

      <div class="qwrap questions">
        {{-- Guests cannot see the vote arrows --}}
        @if(Sentry::check())
          <div class="arrowbox">
            {{HTML::linkRoute('vote','',array('up', $question->id),array('class'=>'like', 'title'=>'Upvote'))}}
            {{HTML::linkRoute('vote','',array('down',$question->id),array('class'=>'dislike','title'=>'Downvote'))}}
          </div>
        @endif

        {{-- class will differ on the situation --}}
        @if($question->votes > 0)
          <div class="cntbox cntgreen">
        @elseif($question->votes == 0)
          <div class="cntbox">
        @else
          <div class="cntbox cntred">
        @endif
        <div class="cntcount">{{$question->votes}}</div>
        <div class="cnttext">vote</div>
        </div>

        {{--Answer section will be filled later in this chapter--}}
        <div class="cntbox">
          <div class="cntcount">0</div>
          <div class="cnttext">answer</div>
        </div>

        <div class="qtext">
          <div class="qhead">
            {{HTML::linkRoute('question_details',$question->title,array($question->id,Str::slug($question->title)))}}
          </div>
          <div class="qinfo"">Asked by <a href="#">{{$asker->first_name.' '.$asker->last_name}}</a> around {{date('m/d/Y H:i:s',strtotime($question->created_at))}}</div>
          @if($tags!=null)
            <ul class="qtagul">
              @foreach($tags as $tag)
                <li>{{HTML::linkRoute('tagged',$tag->tag,$tag->tagFriendly)}}</li>
              @endforeach
            </ul>
          @endif
        </div>
      </div>
    @endforeach

    {{-- and lastly, the pagination --}}
    {{$questions->links()}}

  @else
    No questions found. {{HTML::linkRoute('ask','Ask a question?')}}
  @endif

@stop

由于我们已经设置了关系,我们可以直接使用$question->users来访问提问者,或者$question->tags来直接访问问题的标签。

links()方法带来了 Laravel 内置的分页系统。该系统已准备好与 Bootstrap 一起使用。此外,我们可以从app/config下的view.php文件中修改其外观。

如果你一直跟到这里,当你导航到你的索引页面,在插入一些新问题后,你会看到一个如下截图的视图:

创建我们的问题列表页面

现在,我们需要为点赞和踩按钮添加功能。

添加点赞和踩功能

点赞和踩按钮将出现在我们项目的几乎每个页面上,因此将它们添加到主页面是一个更好的做法,而不是在每个模板中多次添加和克隆它们。

为了做到这一点,打开app/views下的template_masterpage.php文件,并找到以下行:

@yield('footer_assets')

在上一段代码下面添加以下代码:

{{-- if the user is logged in and on index or question details page--}}
@if(Sentry::check() && (Route::currentRouteName() == 'index' || Route::currentRouteName() == 'question_details'))
  <script type="text/javascript">
    $('.questions .arrowbox .like, .questions .arrowbox .dislike').click(function(e){
      e.preventDefault();
      var $this = $(this);
      $.get($(this).attr('href'),function($data){
        $this.parent('.arrowbox').next('.cntbox').find('.cntcount').text($data);
      }).fail(function(){
        alert('An error has been occurred, please try again later');
      });
    });
  </script>
@endif

在前面的代码中,我们检查用户是否已登录,以及用户是否已导航到索引或详细页面。然后我们使用 JavaScript 防止用户点击链接,并修改点击事件为 Ajax get()请求。在下一段代码中,我们将用来自Ajax()请求的结果来填充投票的值。

现在我们需要编写投票更新方法,使其正常工作。为此,打开app/controllers下的QuestionsController.php文件,并添加以下代码:

/**
  * Vote AJAX Request
**/
public function getVote($direction,$id) {

  //request has to be AJAX Request
  if(Request::ajax()) {

    $question = Question::find($id);

    //if the question id is valid
    if($question) {

      //new vote count
      if($direction == 'up') {
        $newVote = $question->votes+1;
      } else {
        $newVote = $question->votes-1;
      }

      //now the update
      $update = $question->update(array(
        'votes' => $newVote
      ));

      //we return the new number
      return $newVote;
    } else {
      //question not found
      Response::make("FAIL", 400);
    }
  } else {
    return Redirect::route('index');
  }
}

getVote()方法检查问题是否有效,如果有效,它会增加或减少一个投票计数。我们在这里没有验证参数$direction,因为我们已经在资源的正则表达式中预先过滤了,$direction的值应该是updown

注意

在现实世界的情况下,你甚至应该将投票存储在一个新的表中,并检查用户的投票是否唯一。你还应该确保用户只投一次票。

现在我们的索引页面已经准备就绪并运行,我们可以继续下一步了。

创建我们的问题页面

在详细页面中,我们需要向用户展示完整的问题。还会有一个答案的地方。为了创建我们的问题页面,我们执行以下步骤:

  1. 首先,我们需要添加我们之前在路由中定义的详细方法。将以下代码添加到app/controllers下的QuesionsController.php文件中:
/**
 * Details page
**/
public function getDetails($id,$title) {
  //First, let's try to find the question:
  $question = Question::with('users','tags')->find($id);

  if($question) {

    //We should increase the "viewed" amount
    $question->update(array(
      'viewed' => $question->viewed+1
    ));

    return View::make('qa.details')
      ->with('title',$question->title)
      ->with('question',$question);

  } else {
    return Redirect::route('index')
    ->with('error','Question not found');
  }
}

我们首先尝试使用标签和发布者的信息来获取问题信息。如果找到问题,我们将浏览次数增加一次,然后简单地加载视图,并将标题和问题信息添加到视图中。

  1. 在显示视图之前,我们首先需要一些额外的路由来删除问题和回复帖子。要添加这些,将以下代码添加到app文件夹中的routes.php文件中:
//Reply Question:
Route::post('question/{id}/{title}',array('as'=>'question_reply','before'=>'csrf|user', 'uses'=>'AnswersController@postReply'))->where(array('id'=>'[0-9]+','title'=>'[0-9a-zA-Z\-\_]+'));

//Admin Question Deletion
Route::get('question/delete/{id}',array('as'=>'delete_question','before'=>'access_check:admin','uses'=>'QuestionsController@getDelete'))->where('id','[0-9]+');
  1. 现在控制器方法和视图中所需的路由已经准备好,我们需要视图来向最终用户显示数据。按照步骤,逐部分将所有提供的代码添加到app/views/qa目录下的details.blade.php文件中:
@extends('template_masterpage')

@section('content')

<h1 id="replyh">{{$question->title}}</h1>
<div class="qwrap questions">
  <div id="rcount">Viewed {{$question->viewed}} time{{$question->viewed>0?'s':''}}.</div>

  @if(Sentry::check())
    <div class="arrowbox">
      {{HTML::linkRoute('vote',''array('up',$question->id),array('class'=>'like', 'title'=>'Upvote'))}}
      {{HTML::linkRoute('vote','',array('down',$question->id),array('class'=>'dislike','title'=>'Downvote'))}}
    </div>
  @endif

  {{-- class will differ on the situation --}}
  @if($question->votes > 0)
    <div class="cntbox cntgreen">
  @elseif($question->votes == 0)
    <div class="cntbox">
  @else
    <div class="cntbox cntred">
  @endif
      <div class="cntcount">{{$question->votes}}</div>
      <div class="cnttext">vote</div>
    </div>

在视图的第一部分,我们将视图文件扩展到我们的主页面template_masterpage。然后我们开始填写content部分的代码。我们使用命名路由创建了两个链接,用于投票和反对票,这将使用 Ajax 处理。此外,由于每种投票状态都有不同的样式(正面投票为绿色,负面投票为红色),我们使用if子句并修改了开放的<div>标签。

  1. 现在将以下代码添加到details.blade.php中:
  <div class="rblock">
    <div class="rbox">
      <p>{{nl2br($question->question)}}</p>
    </div>
    <div class="qinfo">Asked by <a href="#">{{$question->users->first_name.' '.$question->users->last_name}}</a> around {{date('m/d/Y H:i:s',strtotime($question->created_at))}}</div>

    {{--if the question has tags, show them --}}
    @if($question->tags!=null)
      <ul class="qtagul">
        @foreach($question->tags as $tag)
          <li>{{HTML::linkRoute('tagged',$tag->tag,$tag->tagFriendly)}}</li>
        @endforeach
      </ul>
    @endif

在这一部分,我们展示问题本身,并检查是否有标签。如果tags对象不为空(存在标签),我们为每个标签使用命名路由创建一个链接,以显示带有$tag标签的问题。

  1. 现在将以下代码添加到details.blade.php中:
    {{-- if the user/admin is logged in, we will have a buttons section --}}
    @if(Sentry::check())
      <div class="qwrap">
        <ul class="fastbar">
          @if(Sentry::getUser()->hasAccess('admin'))
            <li class="close">{{HTML::linkRoute('delete_question','delete',$question->id)}}</li>
          @endif
          <li class="answer"><a href="#">answer</a></li>
        </ul>
      </div>
    @endif
  </div>
  <div id="rreplycount">{{count($question->answers)}} answers</div>

在这一部分,如果最终用户是管理员,我们会显示回答和删除问题的按钮。

  1. 现在将以下代码添加到details.blade.php中:
  {{-- if it's a user, we will also have the answer block inside our view--}}
  @if(Sentry::check())
    <div class="rrepol" id="replyarea" style="margin-bottom:10px">
      {{Form::open(array('route'=>array('question_reply',$question->id,Str::slug($question->title))))}}
      <p class="minihead">Provide your Answer:</p>
      {{Form::textarea('answer',Input::old('answer'),array('class'=>'fullinput'))}}
      {{Form::submit('Answer the Question!')}}
      {{Form::close()}}
    </div>
  @endif

</div>
@stop

在这一部分,我们将向问题本身添加回答块,利用 Laravel 4 内置的Form类。这个表单只对已登录的用户可用(也对管理员可用,因为他们也是已登录用户)。我们使用@stop来完成这一部分的内容。

  1. 现在将以下代码添加到details.blade.php中:
@section('footer_assets')

  {{--If it's a user, hide the answer area and make a simple show/hide button --}}
  @if(Sentry::check())
    <script type="text/javascript">

    var $replyarea = $('div#replyarea');
    $replyarea.hide();

    $('li.answer a').click(function(e){
      e.preventDefault();

      if($replyarea.is(':hidden')) {
        $replyarea.fadeIn('fast');
      } else {
        $replyarea.fadeOut('fast');
      }
    });
    </script>
  @endif

  {{-- If the admin is logged in, make a confirmation to delete attempt --}}
  @if(Sentry::check())
    @if(Sentry::getUser()->hasAccess('admin'))
      <script type="text/javascript">
      $('li.close a').click(function(){
        return confirm('Are you sure you want to delete this? There is no turning back!');
      });
      </script>
    @endif
  @endif
@stop

在这一部分,我们填充footer_assets部分以添加一些 JavaScript 来向用户显示/隐藏答案字段,并在删除问题之前向管理员显示确认框。

如果所有步骤都已完成,您应该有一个如下截图所示的视图:

创建我们的问题页面

最后,我们需要一个删除问题的方法。将以下代码添加到app/controllers目录下的QuestionsController.php文件中:

/**
 * Deletes the question
**/

public function getDelete($id) {
  //First, let's try to find the question:
  $question = Question::find($id);

  if($question) {
    //We delete the question directly
    Question::delete();
    //We won't have to think about the tags and the answers,
    //because they are set as foreign key and we defined them cascading on deletion, 
    //they will be automatically deleted

    //Let's return to the index page with a success message
    return Redirect::route('index')
      ->with('success','Question deleted successfully!');
  } else {
    return Redirect::route('index')
      ->with('error','Nothing to delete!');
  }
}

由于我们已经设置了相关表在删除时级联删除,我们不必担心删除答案和标签。

现在我们准备发布答案,我们应该创建答案表并处理我们的答案。

创建我们的答案表和资源

我们的答案表将与当前的问题表非常相似,只是它将有更少的列。我们的答案也可以被投票,一个答案可以被问题的发布者或管理员标记为最佳答案。为了创建我们的答案表和资源,我们执行以下步骤:

  1. 首先,让我们创建数据库表。在终端中运行以下命令:
**php artisan migrate:make create_answers_table --table=answers --create**

  1. 现在,打开迁移文件,它创建在app/database/migrations目录下,并用以下代码替换up()函数的内容:
Schema::create('answers', function(Blueprint $table)
{
  $table->increments('id');

  //question's id
  $table->integer('questionID')->unsigned()->default(0);
  //answerer's user id
  $table->integer('userID')->unsigned()->default(0);
  $table->text('answer');
  //if the question's been marked as correct
  $table->enum('correct',array('0','1'))->default(0);
  //total number of votes:
  $table->integer('votes')->default(0);
  //foreign keys
  $table->foreign('questionID')->references('id')->on('questions')->onDelete('cascade');
  $table->foreign('userID')->references('id')->on('users')->onDelete('cascade');

  $table->timestamps();
});
  1. 现在,为了从 Eloquent ORM 及其关系中受益,我们需要为answers表创建一个模型。将以下代码添加为app/models目录下的Answer.php文件:
<?php

class Answer extends Eloquent {

  //The relation with users
  public function users() {
    return $this->belongsTo('User','userID');
  }

  //The relation with questions
  public function questions() {
    return $this->belongsTo('Question','questionID');
  }

  //which fields can be filled
  protected $fillable = array('questionID', 'userID', 'answer', 'correct', 'votes');

  //Answer Form Validation Rules
  public static $add_rules = array(
    'answer'	=> 'required|min:10'
  );

}

答案是用户和问题的子级,这就是为什么在我们的模型中,我们应该使用belongsTo()来关联他们的表。

  1. 由于一个问题可能有多个答案,我们还应该从questions表到answers表添加一个关系(以获取关于问题的答案数据,您问题的所有答案,或我赞过的问题的所有答案)。为此,打开app/models目录下的Question.php文件,并添加以下代码:
public function answers() {
  return $this->hasMany('Answer','questionID');
}
  1. 最后,我们需要一个控制器来处理与答案相关的请求。在终端中运行以下命令以为答案创建一个控制器:
**php artisan controller:make AnswersController**

这个命令将在app/controllers目录下创建一个AnswersController.php文件。

现在我们的答案资源已经准备好,我们可以处理答案了。

处理答案

在上一节中,我们成功地创建了一个带有标签的问题和我们的答案形式。现在我们需要处理答案并将它们添加到数据库中。有一些简单的步骤需要遵循:

  1. 首先,我们需要一个控制器表单来处理答案并将其添加到表中。为此,请打开app/controllers目录下新创建的AnswersController.php文件,删除类内部的每个自动生成的方法,并在类定义内添加以下代码:
/**
 * Adds a reply to the questions
**/
public function postReply($id,$title) {

  //First, let's check if the question id is valid
  $question = Question::find($id);

  //if question is found, we keep on processing
  if($question) {

    //Now let's run the form validation
    $validation = Validator::make(Input::all(), Answer::$add_rules);

    if($validation->passes()) {

      //Now let's create the answer
      Answer::create(array('questionID' => $question->id,'userID' => Sentry::getUser()->id,'answer' => Input::get('answer')
      ));

      //Finally, we redirect the user back to the question page with a success message
      return Redirect::route('question_details',array($id,$title))
        ->with('success','Answer submitted successfully!');

    } else {
      return Redirect::route('question_details',array($id,$title))
        ->withInput()
        ->with('error',$validation->errors()->first());
    }

  } else {
    return Redirect::route('index')
      ->with('error','Question not found');
  }

}

postReply()方法简单地检查问题是否有效,运行表单验证,将一个答案添加到数据库,并将用户返回到问题页面。

  1. 现在在问题页面中,我们还需要包括答案和答案数量。但在此之前,我们需要先获取它们。有一些步骤需要完成。

  2. 首先,打开app/controllers目录下的QuestionsController.php文件,并找到以下行:

       $question = Question::with('users','tags')->find($id);

用以下行替换上一行:

       $question = Question::with('users','tags','answers')->find($id);
  1. 现在,在app/controllers目录下的MainController.php文件中找到以下行:
      ->with('questions',Question::with('users','tags')-> orderBy('id','desc')->paginate(2));

用以下行替换上一行:

     ->with('questions',Question::with('users', 'tags', 'answers')->orderBy('id','desc')->paginate(2));
  1. 现在打开app/views/qa目录下的index.blade.php文件,并找到以下代码:
      {{--Answer section will be filled later in this chapter--}}
      <div class="cntbox">
        <div class="cntcount">0</div>
        <div class="cnttext">answer</div>
      </div>

用以下代码替换上一段代码:

       <?php
       //does the question have an accepted answer?
       $answers = $question->answers; 
       $accepted = false; //default false

       //We loop through each answer, and check if there is an accepted answer
       if($question->answers!=null) {
         foreach ($answers as $answer) {
           //If an accepted answer is found, we break       the loop
           if($answer->correct==1) {
             $accepted=true;
             break;
           }
         }
       }
       ?>
       @if($accepted)
         <div class="cntbox cntgreen">
       @else
         <div class="cntbox cntred">
       @endif
         <div class="cntcount">{{count($answers)}}</div>
         <div class="cnttext">answer</div>
       </div>

在这个修改中,我们添加了一个 PHP 代码和一个循环,检查每个答案是否被接受。如果是,我们就改变div的容器类。此外,我们还添加了一个显示答案数量的功能。

  1. 接下来,我们需要定义路由资源来处理答案的点赞和踩和选择最佳答案。将以下代码添加到app文件夹下的routes.php文件中:
       //Answer upvoting and Downvoting
       Route::get('answer/vote/{direction}}/{id}',array('as'=>'vote_answer', 'before'=>'user', 'uses'=>'AnswersController@getVote'))->where(array('direction'=>'(up|down)', 'id'=>'[0-9]+'));
  1. 现在我们需要在问题详情页面中显示答案,以便用户可以看到答案。为此,请打开app/views/qa目录下的details.blade.php文件,并执行以下步骤:

  2. 首先,找到以下行:

       <div id="rreplycount">0 answers</div>

用以下行替换上一行:

       <div id="rreplycount">{{count($question->answers)}} answers</div>
  1. 现在找到以下代码:
       </div>
       @stop

       @section('footer_assets')

在上一行上面添加以下代码:

       @if(count($question->answers))
         @foreach($question->answers as $answer)

           @if($answer->correct==1)
             <div class="rrepol correct">
           @else
             <div class="rrepol">
    @endif
           @if(Sentry::check())
             <div class="arrowbox">
               {{HTML::linkRoute('vote_answer','',array('up', $answer->id),array('class'=>'like', 'title'=>'Upvote'))}}
               {{HTML::linkRoute('vote_answer','', array('down',$answer->id), array('class'=>'dislike','title'=>'Downvote'))}}

             </div>
           @endif

           <div class="cntbox">
             <div class="cntcount">{{$answer->votes}}</div>
             <div class="cnttext">vote</div>
           </div>

           @if($answer->correct==1)
             <div class="bestanswer">best answer</div>
           @else
             {{-- if the user is admin or the owner of the question, show the best answer button --}}
             @if(Sentry::check())
               @if(Sentry::getUser()->hasAccess('admin') || Sentry::getUser()->id == $question->userID)
                   <a class="chooseme" href="{{URL::route('choose_answer',$answer->id)}}"><div class="choosebestanswer">choose</div></a>
               @endif
             @endif
           @endif
           <div class="rblock">
             <div class="rbox">
               <p>{{nl2br($answer->answer)}}</p>
             </div>
             <div class="rrepolinf">
             <p>Answered by <a href="#">{{$answer->users->first_name.' '.$answer->users->last_name}}</a> around {{date('m/d/Y H:i:s',strtotime($answer->created_at))}}</p>
             </div>
           </div>
         </div>
         @endforeach
       @endif

答案的当前结构与我们在本章前面创建的问题结构非常接近。此外,我们有一个按钮可以选择最佳答案,只有提问者和管理员才能看到。

  1. 现在,我们需要在同一个视图中添加一个确认按钮。为此,请将以下代码添加到footer_assets部分:
       {{-- for admins and question owners --}}
       @if(Sentry::check())
         @if(Sentry::getUser()->hasAccess('admin') || Sentry::getUser()->id == $question->userID)
           <script type="text/javascript">
             $('a.chooseme').click(function(){
               return confirm('Are you sure you want to choose this answer as best answer?');
             });
           </script>
         @endif
       @endif
  1. 现在,我们需要一个方法来增加或减少答案的投票数。将以下代码添加到app/controllers目录下的AnswersController.php文件中:
/**
  * Vote AJAX Request
**/
public function getVote($direction, $id) {

  //request has to be AJAX Request
  if(Request::ajax()) {
    $answer = Answer::find($id);
    //if the answer id is valid
    if($answer) {
      //new vote count
      if($direction == 'up') {
        $newVote = $answer->votes+1;
      } else {
        $newVote = $answer->votes-1;
      }

      //now the update
      $update = $answer->update(array(
        'votes' => $newVote
      ));

      //we return the new number
      return $newVote;
    } else {
      //answer not found
      Response::make("FAIL", 400);
    }
  } else {
    return Redirect::route('index');
  }
}

getVote()方法与问题投票方法完全相同。这里唯一的区别是,影响的是答案而不是问题。

选择最佳答案

我们需要一个处理方法来选择最佳答案。为了选择最佳答案,我们执行以下步骤:

  1. 打开app/controllers目录下的AnswersController.php文件,并添加以下代码:
/**
  * Chooses a best answer
**/
public function getChoose($id) {

  //First, let's check if there is an answer with that given ID
  $answer = Answer::with('questions')->find($id);

  if($answer) {
    //Now we should check if the user who clicked is an admin or the owner of the question 
    if(Sentry::getUser()->hasAccess('admin') || $answer->userID == Sentry::getUser()->id) {
        //First we should unmark all the answers of the question from correct (1) to incorrect (0)
        Answer::where('questionID',$answer->questionID)
          ->update(array(
            'correct' => 0
          ));

        //And we should mark the current answer as correct/best answer
      $answer->update(array(
        'correct' => 1
      ));

      //And now let's return the user back to the questions page
      return Redirect::route('question_details',array($answer->questionID, Str::slug($answer->questions->title)))
          ->with('success','Best answer chosen successfully');
    } else {
      return Redirect::route('question_details',array($answer->questionID, Str::slug($answer->questions->title)))
        ->with('error','You don\'t have access to this attempt!');
    }

  } else {
    return Redirect::route('index')
      ->with('error','Answer not found');
  }

}

在上述代码中,我们首先检查答案是否有效。然后,我们检查点击最佳答案按钮的用户是否是问题的提问者或应用程序的管理员。之后,我们将问题的所有答案标记为未选中(清除问题的所有最佳答案信息),并将选择的答案标记为最佳答案。最后,我们返回带有成功消息的表单。

  1. 现在,我们需要一个方法来删除答案。首先,我们需要一个路由。打开app目录下的routes.php文件,并添加以下代码:
//Deleting an answer
Route::get('answer/delete/{id}',array('as'=>'delete_answer','before'=>'user', 'uses'=> 'AnswersController@getDelete'))->where('id','[0-9]+');
  1. 接下来,在app/views/qa下的details.blade.php文件中找到以下代码:
<p>Answered by <a href="#">{{$answer->users->first_name.' '.$answer->users->last_name}}</a> around {{date('m/d/Y H:i:s',strtotime($answer->created_at))}}</p>

在之前的代码下面添加以下代码:

{{-- Only the answer's owner or the admin can delete the answer --}}
@if(Sentry::check())
  <div class="qwrap">
    <ul class="fastbar">
      @if(Sentry::getUser()->hasAccess('admin') || Sentry::getUser()->id == $answer->userID)
        <li class="close">{{HTML::linkRoute('delete_answer','delete',$answer->id)}}</li>
      @endif
    </ul>
  </div>
@endif
  1. 现在,我们需要一个控制器方法来删除答案。在app/controllers下的AnswersController.php文件中添加以下代码:
/**
 * Deletes an answer
**/
public function getDelete($id) {

  //First, let's check if there is an answer with that given ID
  $answer = Answer::with('questions')->find($id);

  if($answer) {
    //Now we should check if the user who clicked is an admin or the owner of the question 
    if(Sentry::getUser()->hasAccess('admin') || $answer->userID==Sentry::getUser()->id) {

      //Now let's delete the answer
      $delete = Answer::find($id)->delete();

      //And now let's return the user back to the questions page
      return Redirect::route('question_details',array($answer->questionID, Str::slug($answer->questions->title)))
        ->with('success','Answer deleted successfully');
    } else {
      return Redirect::route('question_details',array($answer->questionID, Str::slug($answer->questions->title)))
        ->with('error','You don\'t');
    }

  } else {
    return Redirect::route('index')
      ->with('error','Answer not found');
  }
}

如果你已经做了一切正确,我们详情页面的最终版本将会像下面的截图一样:

选择最佳答案

现在一切准备就绪,可以提问、回答、标记最佳答案和删除,我们应用中只缺少一个功能,即标签搜索。正如你所知,我们已经将所有标签都做成了链接,所以现在我们应该处理它们的路由。

通过标签搜索问题

在我们的主页面和详情页面中,我们给所有标签都加了一个特殊链接。我们将执行以下步骤来通过标签搜索问题:

  1. 首先,打开app/controllers下的QuestionsController.php文件,并添加以下代码:
/**
  * Shows the questions tagged with $tag friendly URL
**/
public function getTaggedWith($tag) {

  $tag = Tag::where('tagFriendly',$tag)->first();

  if($tag) {
    return View::make('qa.index')
      ->with('title','Questions Tagged with: '.$tag->tag)
      ->with('questions',$tag->questions()->with('users','tags','answers')->paginate(2));
  } else {
    return Redirect::route('index')
      ->with('error','Tag not found');
  }
}

这段代码的作用是,首先使用列tagFriendly搜索标签,这会得到一个唯一的结果。因此,我们可以安全地使用first()返回第一个结果。然后我们检查标签是否存在于我们的系统中。如果没有,我们会返回用户到索引页面,并显示一个错误消息,说明未找到该标签。

如果找到了标签,我们使用我们定义的关系捕获所有使用该标签标记的问题,并使用急加载来加载用户、标签(所有问题的标签)和答案(尽管我们在这个页面上不显示答案,但我们需要它们的计数来在页面上显示)。我们的视图将与索引页面的视图完全相同。因此,我们直接使用了那个视图,而不是创建一个新的。

我们将分页限制保持为两,只是为了展示它的工作原理。

  1. 最后,为了允许页面上的 JavaScript 资源(例如启用 Ajax 投票和取消投票),打开app/views下的template_masterpage.php文件,并找到以下行:
@if(Sentry::check() && (Route::currentRouteName() == 'index' || Route::currentRouteName() == 'question_details'))

用以下代码替换之前的代码:

@if(Sentry::check() && (Route::currentRouteName() == 'index' || Route::currentRouteName() == 'tagged' || Route::currentRouteName() == 'question_details'))

这样,我们甚至可以在具有名称为tagged的路由的页面上允许这些 Ajax 事件。

如果你已经做了一切正确,当你点击标签的名称时,会出现如下页面:

通过标签搜索问题

摘要

在本章中,我们使用了 Laravel 4 的各种功能。我们学会了去除公共部分,使 Laravel 可以在一些共享主机解决方案上运行。我们还学会了 Sentry 2 的基础知识,这是一个强大的身份验证类。我们学会了如何使用多对多关系和中间表。我们还使用了 Eloquent ORM 来定义属于和拥有任何关系。我们使用资源来定义所有的 URL、表单操作和链接。因此,如果你需要更改应用程序的 URL 结构(比如你需要将你的网站更改为德语,而德语中的问题是“frage”),你只需要编辑routes.php。这样一来,你就不需要深入每个文件来修复链接。我们使用分页类来浏览记录,还使用了 Laravel 表单构建器类。

在下一章中,我们将使用我们到目前为止学到的一切来开发一个功能齐全的电子商务网站。

第九章:构建 RESTful API - 电影和演员数据库

设计和开发成功的 RESTful API 通常非常困难。设计和编写成功的 RESTful API 有许多方面;例如,保护和限制 API。在本章中,我们将专注于使用 Laravel 编写一个简单的电影和演员 API 的 REST 的基础知识。我们将在一个基本的认证系统后面创建一些 JSON 端点,并学习一些 Laravel 4 的技巧。本章将涵盖以下主题:

  • 创建和迁移用户数据库

  • 配置用户模型

  • 添加样本用户

  • 创建和迁移电影数据库

  • 创建一个电影模型

  • 添加样本电影

  • 创建和迁移演员数据库

  • 创建一个演员模型

  • 将演员分配给电影

  • 理解认证机制

  • 查询 API

创建和迁移用户数据库

我们假设您已经在app/config/目录下的database.php文件中定义了数据库凭据。对于这个应用程序,我们需要一个数据库。您可以通过简单运行以下 SQL 命令来创建一个新的数据库,或者您可以使用您的数据库管理界面,比如 phpMyAdmin:

**CREATE DATABASE laravel_api**

成功创建应用程序的数据库后,首先我们需要为应用程序生成一个应用程序密钥。正如您从之前的章节中了解的那样,这对于我们应用程序的安全和认证类是必要的。要做到这一点,首先打开您的终端,导航到您的项目文件夹,并运行以下命令:

**php artisian key:generate**

如果没有错误发生,我们应该编辑认证类的配置文件。为了使用 Laravel 内置的认证类,我们需要编辑配置文件auth.php,该文件位于app/config/。该文件包含了认证设施的几个选项。如果您需要更改表名等,您可以在auth.php文件中进行更改。默认情况下,Laravel 带有一个用户模型;您可以看到位于app/models/User.php文件。使用 Laravel 4,我们需要定义哪些字段可以在我们的User模型中填充。让我们编辑app/models/User.php并添加"fillable"数组:

<?php

use Illuminate\Auth\UserInterface;
use Illuminate\Auth\Reminders\RemindableInterface;

class User extends Eloquent implements UserInterface, RemindableInterface {

  /**
   * The database table used by the model.
   *
   * @var string
   */
  protected $table = 'users';

  /**
   * The attributes excluded from the model's JSON form.
   *
   * @var array
   */
  protected $hidden = array('password');

  // Specify which attributes should be mass-assignable
   protected $fillable = array('email', 'password');

  /**
   * Get the unique identifier for the user.
   *
   * @return mixed
   */
  public function getAuthIdentifier()
  {
    return $this->getKey();
  }

  /**
   * Get the password for the user.
   *
   * @return string
   */
  public function getAuthPassword()
  {
    return $this->password;
  }

  /**
   * Get the e-mail address where password reminders are sent.
   *
   * @return string
   */
  public function getReminderEmail()
  {
    return $this->email;
  }

}

基本上,我们的 RESTful API 用户需要两列,它们是:

  • email:此列存储作者的电子邮件 ID

  • password:此列用于存储作者的密码

现在我们需要几个迁移文件来创建users表并向我们的数据库添加作者。要创建一个迁移文件,可以使用以下命令:

**php artisan migrate:make create_users_table --table=users --create**

打开最近创建的位于app/database/migrations/的迁移文件。我们需要编辑up()函数如下:

  public function up()
  {
    Schema::create('users', function(Blueprint $table)
    {
      $table->increments('id');
      $table->string('email');
      $table->string('password');
      $table->timestamps();
    });
  }

编辑迁移文件后,请运行migrate命令:

**php artisian migrate**

如您所知,该命令将创建users表及其列。如果没有错误发生,请检查laravel_api数据库中的users表和列。

添加样本用户

现在我们需要为向数据库添加一些 API 用户创建一个新的迁移文件:

**php artisan migrate:make add_some_users**

打开迁移文件并编辑up()函数如下:

public function up()
  {
    User::create(array(
            'email' => 'john@gmail.com',
            'password' => Hash::make('johnspassword'),
          ));
    User::create(array(
            'email' => 'andrea@gmail.com',
            'password' => Hash::make('andreaspassword'),
          ));
  }

现在我们有了两个 API 用户用于我们的应用程序。这些用户将可以查询我们的 RESTful API。

创建和迁移电影数据库

对于一个简单的电影和演员应用程序,基本上我们需要两个用于存储数据的表。其中一个是movies表。该表将包含电影的名称和发行年份。

我们需要一个迁移文件来创建我们的movies表和其列。我们将再次使用artisan工具。打开您的终端,导航到您的项目文件夹,并运行以下命令:

php artisan migrate:make create_movies_table --table=movies --create

打开最近创建的位于app/database/migrations/的迁移文件。我们需要编辑up()函数如下:

  public function up()
  {
    Schema::create('movies', function(Blueprint $table)
    {
      $table->increments('id');
      $table->string('name');
      $table->integer('release_year');
      $table->timestamps();
    });
  }

编辑迁移文件后,运行migrate命令:

**php artisian migrate**

创建一个电影模型

正如您所知,对于 Laravel 上的任何与数据库操作相关的事情,使用模型是最佳实践。我们将利用Eloquent ORM的好处。

将以下代码保存在app/models/下的Movie.php文件中:

<?php
class Movie extends Eloquent {

protected $table = 'movies';

protected $fillable = array('name','release_year');

public function Actors(){

      return $this-> belongsToMany('Actor' , 'pivot_table');
}

}

我们使用受保护的$table变量设置了数据库表名。此外,我们使用了可编辑列的$fillable变量,以及时间戳的$timestamps变量,正如我们在前几章中已经看到和使用的那样。在模型中定义的变量足以使用 Laravel 的 Eloquent ORM。我们将在本章的将演员分配给电影部分中介绍公共Actor()函数。

我们的电影模型已经准备好了:现在我们需要一个演员模型及其相应的表。

添加示例电影

现在我们需要为向数据库添加一些电影创建一个新的迁移文件。实际上,您也可以使用数据库种子程序来种子数据库。在这里,我们将使用迁移文件来种子数据库。您可以在以下位置查看种子程序:

laravel.com/docs/migrations#database-seeding

运行以下migrate命令:

**php artisan migrate:make add_some_movies**

打开迁移文件并编辑up()函数如下:

  public function up()
  {
    Movie::create(array(
            'name' => 'Annie Hall',
        'release_year' => '1977'
          ));

    Movie::create(array(
            'name' => ' Manhattan ',
        'release_year' => '1978'
          ));

Movie::create(array(
            'name' => 'The Shining',
        'release_year' => '1980'
          ));
  }

创建和迁移演员数据库

我们需要创建一个包含电影演员姓名的actors表。我们需要一个迁移文件来创建我们的movies表和列。我们将使用artisan工具再次进行操作。让我们打开终端,导航到我们的项目文件夹,并运行以下命令:

php artisan migrate:make create_actors_table --table=actors –create

打开最近创建并位于app/database/migrations/中的迁移文件。我们需要编辑up()函数如下:

  public function up()
  {
    Schema::create('actors', function(Blueprint $table)
    {
      $table->increments('id');
      $table->string('name');
      $table->timestamps();
    });
  }

编辑迁移文件后,运行以下migrate命令:

php artisian migrate

创建演员模型

要创建演员模型,请将以下代码保存为Movies.php,并放在app/models/下:

<?php
class Actor extends Eloquent {

protected $table = 'actors';

protected $fillable = array('name');

public function Movies(){

      return $this-> belongsToMany('Movies', 'pivot_table');
}

}

将演员分配给电影

正如您所知,我们在演员和电影模型之间使用了belongsToMany关系。这是因为一个演员可能出演了许多电影。一部电影也可能有许多演员。

正如您在本章的前几节中所看到的,我们使用了一个名为pivot_table的中间表。我们也可以使用artisan工具创建中间表。让我们创建它:

php artisan migrate:make create_pivot_table --table=pivot_table --create

打开最近创建并位于app/database/migrations/中的迁移文件。我们需要编辑up()函数如下:

  public function up()
  {
    Schema::create('pivot_table', function(Blueprint $table)
    {
      $table->increments('id');
      $table->integer('movies_id');
      $table->integer('actors_id');
      $table->timestamps();
    });  
}

编辑迁移文件后,运行migrate命令:

php artisian migrate

现在我们需要为向数据库添加一些演员创建一个新的迁移文件:

php artisan migrate:make add_some_actors

打开迁移文件并编辑up()函数如下:

  public function up()
  {
    $woody = Actor::create(array(
            'name' => 'Woody Allen'
          ));

    $woody->Movies()->attach(array('1','2'));

    $diane = Actor::create(array(
            'name' => 'Diane Keaton'
          ));

$diane->Movies()->attach(array('1','2'));

$jack = Actor::create(array(
            'name' => 'Jack Nicholson'
        ));

$jack->Movies()->attach(3);

}

让我们获取迁移文件。当我们将users附加到movies时,我们必须使用以下显示的电影 ID:

$voody = Actor::create(array(
            'name' => 'Woody Allen'
          ));

$voody->Movies()->attach(array('1','2'));

这意味着Woody Allen在两部电影中扮演了角色,这两部电影的 ID 分别为12。此外,Diane Keaton也在这两部电影中扮演了角色。但是Jack NicholsonThe Shining中扮演了角色,电影的 ID 为3。正如我们在第八章中已经详细阐述的那样,我们的关系类型是Eloquent belongsToMany关系。

理解认证机制

与许多其他 API 一样,我们的 API 系统是基于认证的。正如您可能还记得前几章所述,Laravel 带有认证机制。在本节中,我们将使用 Laravel 的基于模式的路由过滤功能来保护和限制我们的 API。首先,我们需要编辑我们应用程序的auth.basic过滤器。

打开位于app/filters.php的路由过滤器配置文件,并编辑auth.basic过滤器如下:

Route::filter('auth.basic', function()
{
  return Auth::basic('email');
});

API 用户应该在其请求中发送他们的电子邮件 ID 和密码到我们的应用程序。由于请求,我们编辑了过滤器。API 请求将如下所示:

**curl -i –user andrea@gmail.com:andreaspassword localhost/api/getactorinfo/Woody%20Allen**

现在,我们需要在我们的路由上应用一个过滤器。打开位于app/routes.php的路由过滤器配置文件,并添加以下代码:

Route::when('*', 'auth.basic');

这段代码表明我们的应用程序需要对其上的每个请求进行身份验证。现在我们需要编写我们的路由。将以下行添加到app/routes.php

Route::get('api/getactorinfo/{actorname}', array('uses' => 'ActorController@getActorInfo'));
Route::get('api/getmovieinfo/{moviename}', array('uses' => 'MovieController@getMovieInfo'));
Route::put('api/addactor/{actorname}', array('uses' => 'ActorController@putActor'));
Route::put('api/addmovie/{moviename}/{movieyear}', array('uses' => 'MovieController@putMovie'));
Route::delete('api/deleteactor/{id}', array('uses' => 'ActorController@deleteActor'));
Route::delete('api/deletemovie/{id}', array('uses' => 'MovieController@deleteMovie'));

查询 API

我们需要两个控制器文件来处理我们的 RESTful 路由函数。让我们在app/controllers/下创建两个控制器文件。文件应命名为MovieController.phpActorController.php

从 API 获取电影/演员信息

首先,我们需要getActorInfo()getMovieInfo()函数,以从数据库中获取演员和电影信息。打开位于app/controllers/ActorController.php文件,并写入以下代码:

<?php

class ActorController extends BaseController {
public function getActorInfo($actorname){

$actor = Actor::where('name', 'like', '%'.$actorname.'%')->first();
if($actor){

$actorInfo = array('error'=>false,'Actor Name'=>$actor->name,'Actor ID'=>$actor->id);
$actormovies = json_decode($actor->Movies);
foreach ($actormovies as $movie) {
$movielist[] = array("Movie Name"=>$movie->name, "Release Year"=>$movie->release_year);
}
$movielist =array('Movies'=>$movielist);
return Response::json(array_merge($actorInfo,$movielist));

}
else{

return Response::json(array(
'error'=>true,
'description'=>'We could not find any actor in database like :'.$actorname
));
}
}
}

接下来,打开位于app/controllers/MovieController.php文件,并写入以下代码:

<?php

class MovieController extends BaseController {

public function getMovieInfo($moviename){
$movie = Movie::where('name', 'like', '%'.$moviename.'%')->first();
if($movie){

$movieInfo = array('error'=>false,'Movie Name'=>$movie->name,'Release Year'=>$movie->release_year,'Movie ID'=>$movie->id);
$movieactors = json_decode($movie->Actors);
foreach ($movieactors as $actor) {
$actorlist[] = array("Actor"=>$actor->name);
}
$actorlist =array('Actors'=>$actorlist);
return Response::json(array_merge($movieInfo,$actorlist));

}
else{

return Response::json(array(
'error'=>true,
'description'=>'We could not find any movie in database like :'.$moviename
));
}
}
}

getActorInfo()getMovieInfo()函数基本上是在数据库中搜索具有给定文本的电影/演员名称。如果找到这样的电影或演员,它将以 JSON 格式返回。因此,要从 API 中获取演员信息,我们的用户可以进行如下请求:

**curl -i –-user andrea@gmail.com:andreaspassword localhost/api/getactorinfo/Woody**

演员信息请求的响应将如下所示:

{
   "error":false,
   "Actor Name":"Woody Allen",
   "Actor ID":1,
   "Movies":[
      {
         "Movie Name":"AnnieHall",
         "Release Year":1977
      },
      {
         "Movie Name":"Manhattan",
         "Release Year":1978
      }
   ]
}

任何电影的请求将类似于这样:

**curl -i --user andrea@gmail.com:andreaspassword localhost/api/getmovieinfo/Manhattan**

电影信息请求的响应将如下所示:

{
   "error":false,
   "Movie Name":"Manhattan",
   "Release Year":1978,
   "Movie ID":2,
   "Actors":[
      {
         "Actor":"Woody Allen"
      },
      {
         "Actor":"Diane Keaton"
      }
   ]
}

如果任何用户从数据库中不存在的 API 请求电影信息,响应将如下所示:

{
   "error":true,
   "description":"We could not find any movie in database like :Terminator"
}

对于数据库中不存在的演员,也将有类似的响应:

{
   "error":true,
   "description":"We could not find any actor in database like :Al Pacino"
}

将新电影/演员发送到 API 的数据库

我们需要putActor()putMovie()函数,以允许用户向我们的数据库添加新的演员/电影。

打开位于app/controllers/ActorController.php文件,并添加以下函数:

public function putActor($actorname)
{

$actor = Actor::where('name', '=', $actorname)->first();
if(!$actor){

$the_actor = Actor::create(array('name'=>$actorname));

return Response::json(array(
'error'=>false,
'description'=>'The actor successfully saved. The ID number of Actor is : '.$the_actor->id
));

}
else{

return Response::json(array(
'error'=>true,
'description'=>'We have already in database : '.$actorname.'. The ID number of Actor is : '.$actor->id
));
}
}

现在打开位于app/controllers/MovieController.php文件,并添加以下函数:

public function putMovie($moviename,$movieyear)
{

$movie = Movie::where('name', '=', $moviename)->first();
if(!$movie){

$the_movie = Movie::create(array('name'=>$moviename,'release_year'=>$movieyear));

return Response::json(array(
'error'=>false,
'description'=>'The movie successfully saved. The ID number of Movie is : '.$the_movie->id
));

}
else{

return Response::json(array(
'error'=>true,
'description'=>'We have already in database : '.$moviename.'. The ID number of Movie is : '.$movie->id
));
}
}

putActor()putMovie()函数基本上是在数据库中搜索具有给定文本的电影/演员名称。如果找到电影或演员,函数将以 JSON 格式返回其 ID,否则将创建新的演员/电影并以新记录 ID 响应。因此,要在 API 数据库中创建新的演员,我们的用户可以进行如下请求:

**curl –i –X PUT –-user andrea@gmail.com:andreaspassword localhost/api/addactor/Al%20Pacino**

电影信息请求的响应将如下所示:

{
   "error":false,
   "description":"The actor successfully saved. The ID number of Actor is : 4"
}

如果任何 API 用户尝试添加已存在的演员,API 将如下响应:

{
   "error":true,
   "description":"We have already in database : Al Pacino. The ID number of Actor is : 4"
}

此外,API 数据库中创建新电影的响应应该如下所示:

curl -i –X PUT –-user andrea@gmail.com:andreaspassword localhost/api/addmovie/The%20Terminator/1984

请求的响应将如下所示:

{
   "error":false,
   "description":"The movie successfully saved. The ID number of Movie is : 4"
}

如果任何 API 用户尝试添加已存在的演员,API 将如下响应:

{
   "error":true,
   "description":"We have already in database : The Terminator. The ID number of Movie is : 4"
}

从 API 中删除电影/演员

现在我们需要deleteActor()deleteMovie()函数,以允许用户向我们的数据库添加新的演员/电影。

打开app/controllers/下的ActorController.php文件,并添加以下函数:

public function deleteActor($id)
{

$actor = Actor::find($id);
if($actor){

$actor->delete();

return Response::json(array(
'error'=>false,
'description'=>'The actor successfully deleted : '.$actor->name
));

}
else{

return Response::json(array(
'error'=>true,
'description'=>'We could not find any actor in database with ID number :'.$id
));
}
}

添加函数后,位于app/controllers/ActorController.php中的内容应如下所示:

<?php
class ActorController extends BaseController
{
    public function getActorInfo($actorname)
    {
        $actor = Actor::where('name', 'like', '%' . $actorname . '%')->first();
        if ($actor)
        {
            $actorInfo   = array(
                'error' => false,
                'Actor Name' => $actor->name,
                'Actor ID' => $actor->id
            );
            $actormovies = json_decode($actor->Movies);
            foreach ($actormovies as $movie)
            {
                $movielist[] = array(
                    "Movie Name" => $movie->name,
                    "Release Year" => $movie->release_year
                );
            }
            $movielist = array(
                'Movies' => $movielist
            );
            return Response::json(array_merge($actorInfo, $movielist));
        }
        else
        {
            return Response::json(array(
                'error' => true,
                'description' => 'We could not find any actor in database like :' . $actorname
            ));
        }
    }
    public function putActor($actorname)
    {
        $actor = Actor::where('name', '=', $actorname)->first();
        if (!$actor)
        {
            $the_actor = Actor::create(array(
                'name' => $actorname
            ));
            return Response::json(array(
                'error' => false,
                'description' => 'The actor successfully saved. The ID number of Actor is : ' . $the_actor->id
            ));
        }
        else
        {
            return Response::json(array(
                'error' => true,
                'description' => 'We have already in database : ' . $actorname . '. The ID number of Actor is : ' . $actor->id
            ));
        }
    }
    public function deleteActor($id)
    {
        $actor = Actor::find($id);
        if ($actor)
        {
            $actor->delete();
            return Response::json(array(
                'error' => false,
                'description' => 'The actor successfully deleted : ' . $actor->name
            ));
        }
        else
        {
            return Response::json(array(
                'error' => true,
                'description' => 'We could not find any actor in database with ID number :' . $id
            ));
        }
    }
}

现在我们需要MovieController的类似函数。打开位于app/controllers/MovieController.php文件,并添加以下函数:

public function deleteMovie($id)
{

$movie = Movie::find($id);
if($movie){

$movie->delete();

return Response::json(array(
'error'=>false,
'description'=>'The movie successfully deleted : '.$movie->name
));

}
else{

return Response::json(array(
'error'=>true,
'description'=>'We could not find any movie in database with ID number :'.$id
));
}
}

添加函数后,位于app/controllers/ActorController.php中的内容应如下所示:

<?php
class  extends BaseController
{
    public function getMovieInfo($moviename)
    {
        $movie = Movie::where('name', 'like', '%' . $moviename . '%')->first();
        if ($movie)
        {
            $movieInfo   = array(
                'error' => false,
                'Movie Name' => $movie->name,
                'Release Year' => $movie->release_year,
                'Movie ID' => $movie->id
            );
            $movieactors = json_decode($movie->Actors);
            foreach ($movieactors as $actor)
            {
                $actorlist[] = array(
                    "Actor" => $actor->name
                );
            }
            $actorlist = array(
                'Actors' => $actorlist
            );
            return Response::json(array_merge($movieInfo, $actorlist));
        }
        else
        {
            return Response::json(array(
                'error' => true,
                'description' => 'We could not find any movie in database like :' . $moviename
            ));
        }
    }
    public function putMovie($moviename, $movieyear)
    {
        $movie = Movie::where('name', '=', $moviename)->first();
        if (!$movie)
        {
            $the_movie = Movie::create(array(
                'name' => $moviename,
                'release_year' => $movieyear
            ));
            return Response::json(array(
                'error' => false,
                'description' => 'The movie successfully saved. The ID number of Movie is : ' . $the_movie->id
            ));
        }
        else
        {
            return Response::json(array(
                'error' => true,
                'description' => 'We have already in database : ' . $moviename . '. The ID number of Movie is : ' . $movie->id
            ));
        }
    }
    public function deleteMovie($id)
    {
        $movie = Movie::find($id);
        if ($movie)
        {
            $movie->delete();
            return Response::json(array(
                'error' => false,
                'description' => 'The movie successfully deleted : ' . $movie->name
            ));
        }
        else
        {
            return Response::json(array(
                'error' => true,
                'description' => 'We could not find any movie in database with ID number :' . $id
            ));
        }
    }
}

deleteActor()deleteMovie()函数基本上是在数据库中搜索具有给定 ID 的电影/演员。如果有电影或演员,API 将删除演员/电影并以 JSON 格式返回状态。因此,要从 API 中删除演员,我们的用户可以进行如下请求:

**curl –I –X DELETE –-user andrea@gmail.com:andreaspassword localhost/api/deleteactor/4**

请求的响应将如下所示:

{
   "error":false,
   "description":"The actor successfully deleted : Al Pacino"
}

此外,从 API 数据库中删除电影的响应应如下所示:

**curl –I –X DELETE –-user andrea@gmail.com:andreaspassword localhost/api/deletemovie/4**

请求的响应将如下所示:

{
   "error":false,
   "description":"The movie successfully deleted : The Terminator"
}

如果任何 API 用户尝试从 API 数据库中删除不存在的电影/演员,API 将如下响应:

{
   "error":true,
   "description":"We could not find any movie in database with ID number :17"
}

删除不存在的演员,响应将如下所示:

{
   "error":true,
   "description":"We could not find any actor in database with ID number :58"
}

摘要

在本章中,我们专注于使用 Laravel 编写简单的电影和演员 API 的 REST 基础知识。我们在基本身份验证系统后面创建了一些 JSON 端点,并在本章中学习了一些 Laravel 4 的技巧,比如类似模式的路由过滤。正如你所看到的,使用 Laravel 开发和保护 RESTful 应用程序非常容易。在下一章中,我们将在编写简单的电子商务应用程序时涵盖更有效的 Laravel 方法。

第十章:构建电子商务网站

在本章中,我们将使用 Laravel 编写一个简单的书店示例。我们还将涵盖 Laravel 的内置身份验证、命名路由和数据库种子。我们还将详细介绍一些与 Laravel 一起提供的快速开发方法,例如创建路由 URL。此外,我们将使用一种称为belongs to many的新关系类型进行工作。我们还将涵盖中间表。我们的电子商务应用程序将是一个简单的书店。此应用程序将具有订单、管理和购物车功能。我们将涵盖以下主题:

  • 构建授权系统

  • 创建和迁移作者、书籍、购物车和订单表

  • 创建模板文件

  • 列出书籍

  • 构建购物车

  • 接受订单

  • 列出订单

构建授权系统

我们假设您已经在app/config中的database.php文件中定义了数据库凭据。要创建我们的电子商务应用程序,我们需要一个数据库。您可以创建并简单运行以下 SQL 命令,或者基本上您可以使用数据库管理界面,如 phpMyAdmin:

**CREATE DATABASE laravel_store**

创建和迁移成员数据库

与大多数 PHP 框架相反,Laravel 具有基本且可定制的身份验证机制。身份验证类对于快速开发应用程序非常有帮助。首先,我们需要为我们的应用程序生成一个秘钥。正如我们在前几章中提到的,应用程序的秘钥对于应用程序的安全非常重要,因为所有数据都是使用此秘钥进行哈希加盐的。艺术家可以用一条命令为我们生成这个秘钥:

**php artisan key:generate**

如果没有错误发生,您将看到一条消息,告诉您密钥已成功生成。生成密钥后,如果您之前访问过项目的 URL,但在打开 Laravel 应用程序时遇到问题,只需清除浏览器缓存,然后重试。接下来,我们应该编辑身份验证类的配置文件。要使用 Laravel 内置的身份验证类,我们需要编辑位于app/config/auth.php的配置文件。该文件包含身份验证设施的几个选项。如果您需要更改表名等内容,可以在此文件下进行更改。默认情况下,Laravel 带有一个User模型。您可以在app/models/User.php中找到该文件。使用 Laravel 4,我们需要定义User模型中哪些字段是可填充的。让我们编辑位于app/models/User.php并添加fillable数组:

<?php

use Illuminate\Auth\UserInterface;
use Illuminate\Auth\Reminders\RemindableInterface;

class User extends Eloquent implements UserInterface, RemindableInterface {

  protected $table = 'users';

  /**
   * The attributes excluded from the model's JSON form.
   *
   * @var array
   */
  protected $hidden = array('password');

  //Add to the "fillable" array
   protected $fillable = array('email', 'password', 'name', 'admin');

  /**
   * Get the unique identifier for the user.
   *
   * @return mixed
   */
  public function getAuthIdentifier()
  {
    return $this->getKey();
  }

  /**
   * Get the password for the user.
   *
   * @return string
   */
  public function getAuthPassword()
  {
    return $this->password;
  }

  /**
   * Get the e-mail address where password reminders are sent.
   *
   * @return string
   */
  public function getReminderEmail()
  {
    return $this->email;
  }

}

基本上,我们的成员需要四列。这些是:

  • email:这是用于存储成员电子邮件的列

  • password:这是用于存储成员密码的列

  • name:这是用于存储成员名字和姓氏的列

  • admin:这是用于标记商店管理员的列

现在我们需要几个迁移文件来创建users表并向我们的数据库添加成员。要创建迁移文件,请给出以下命令:

**php artisan migrate:make create_users_table --table=users --create**

打开最近创建的位于app/database/migrations/的迁移文件。我们需要编辑up()函数,如下面的代码片段所示:

  public function up()
  {
    Schema::create('users', function(Blueprint $table)
    {
      $table->increments('id');
      $table->string('email');
      $table->string('password');
      $table->string('name');
      $table->integer('admin');
      $table->timestamps();
    });
  }

编辑迁移文件后,运行migrate命令:

**php artisan migrate**

现在我们需要创建一个数据库种子文件,向数据库添加一些用户。数据库种子是另一种强烈推荐的向应用程序数据库添加数据的方法。数据库种子文件位于app/database/seeds。让我们在UsersTableSeeder.php目录下创建我们的第一个种子文件。

注意

我们可以使用任何名称创建种子文件和种子类。但强烈建议种子文件和类名称应遵循驼峰命名法,例如TableSeeder。遵循全球编程标准将提高您代码的质量。

UsersTableSeeder.php的内容应如下所示:

<?php
Class UsersTableSeeder extends Seeder {

    public function run()
    {
  DB::table('users')->delete();

User::create(array(
            'email' => 'member@email.com',
            'password' => Hash::make('password'),
            'name' => 'John Doe',
            'admin'=>0
        ));

  User::create(array(
            'email' => 'admin@store.com',
            'password' => Hash::make('adminpassword'),
            'name' => 'Jeniffer Taylor',
            'admin'=>1
        ));  

    }

}

要应用种子数据,首先我们需要调用种子类。让我们打开位于app/database/seeds的文件DatabaseSeeder.php并编辑文件如下代码片段所示:

<?php
class DatabaseSeeder extends Seeder {

  /**
   * Run the database seeds.
   *
   * @return void
   */
  public function run()
  {
    Eloquent::unguard();

 **$this->call('UsersTableSeeder');**
 **$this->command->info('Users table seeded!');**
  }

}

安全地存储用户的密码和关键数据非常重要。不要忘记,如果你改变了应用程序密钥,所有现有的哈希记录将无法使用,因为Hash类在验证和存储给定数据时使用应用程序密钥作为盐值密钥。

创建和迁移作者数据库

我们需要一个Author模型来存储书籍作者。它将是一个非常简单的结构。让我们在app/models下创建Author.php文件并添加以下代码:

<?php
Class Author extends Eloquent {

protected $table = 'authors';

protected $fillable = array('name','surname');

}

现在我们需要几个迁移文件来创建authors表并向我们的数据库中添加一些作者。要创建一个迁移文件,输入以下命令:

php artisan migrate:make create_authors_table --table=authors --create

打开最近创建并位于app/database/migrations/目录下的迁移文件。我们需要编辑up()函数如下:

  public function up()
  {
    Schema::create('authors', function(Blueprint $table)
    {
      $table->increments('id');
      $table->string('name');
      $table->string('surname');
      $table->timestamps();
    });
  }

编辑迁移文件后,运行migrate命令:

php artisan migrate

如你所知,该命令会创建authors表及其列。如果没有错误发生,请检查laravel_store数据库中的authors表和列。

向数据库添加作者

现在我们需要创建一个数据库种子文件来向数据库中添加一些作者。让我们在app/database/seeds/下创建我们的第一个种子文件AuthorsTableSeeder.php

AuthorsTableSeeder.php中的内容应该如下所示:

<?php
Class AuthorsTableSeeder extends Seeder {

    public function run()
    {
DB::table('authors')->delete();

        Author::create(array(
            'name' => 'Lauren',
            'surname'=>'Oliver'
        ));

        Author::create(array(
            'name' => 'Stephenie',
            'surname'=>'Meyer'
        ));

        Author::create(array(
            'name' => 'Dan',
            'surname'=>'Brown'
        ));

    }

}

要应用种子数据,首先我们需要调用Seeder类。让我们打开位于app/database/seeds/的文件DatabaseSeeder.php并编辑文件如下代码片段所示:

<?php
class DatabaseSeeder extends Seeder {

  /**
   * Run the database seeds.
   *
   * @return void
   */
  public function run()
  {
    Eloquent::unguard();
    $this->call('UsersTableSeeder');
    $this->command->info('Users table seeded!');
 **$this->call('AuthorsTableSeeder');**
 **$this->command->info('Authors table seeded!');**
  }

}

我们需要使用以下artisan命令向数据库中添加种子数据:

**php artisan db:seed**

注意

当你想要回滚并重新运行所有迁移时,你可以使用以下命令:

**php artisan migrate:refresh --seed**

创建和迁移图书数据库

我们需要一个Book模型来存储作者的书籍。让我们在app/models/下创建Book.php文件并添加以下代码:

<?php
Class Book extends Eloquent {

protected $table = 'books';

protected $fillable = array('title','isbn','cover','price','author_id');

public function Author(){

return $this->belongsTo('Author');

}

}

让我们解释author_id列和Author()函数的作用。正如你从之前的章节中所了解的,Eloquent有几个用于不同类型数据库关系的函数。author_id将存储作者的 ID。Author()函数用于从authors表中获取作者的名字和姓氏。

向数据库添加书籍

现在我们需要创建一个数据库种子文件来向数据库中添加一些书籍。让我们在app/database/seeds/下创建第一个种子文件BooksTableSeeder.php

BooksTableSeeder.php中的内容应该如下所示:

<?php
Class BooksTableSeeder extends Seeder {

  public function run()
  {
  DB::table('books')->delete();

  Book::create(array(
    'title'=>'Requiem',
    'isbn'=>'9780062014535',
    'price'=>'13.40',
    'cover'=>'requiem.jpg',
    'author_id'=>1
   ));
  Book::create(array(
    'title'=>'Twilight',
    'isbn'=>'9780316015844',
    'price'=>'15.40',
    'cover'=>'twilight.jpg',
    'author_id'=>2
  ));
  Book::create(array(
    'title'=>'Deception Point',
    'isbn'=>'9780671027384',
    'price'=>'16.40',
    'cover'=>'deception.jpg',
    'author_id'=>3
  ));

  }

}

要应用种子数据,首先我们需要调用种子类。让我们打开位于app/database/seeds的文件DatabaseSeeder.php并编辑文件如下代码片段所示:

<?php
class DatabaseSeeder extends Seeder {

  /**
   * Run the database seeds.
   *
   * @return void
   */
  public function run()
  {
    Eloquent::unguard();
    $this->call('UsersTableSeeder');
    $this->command->info('Users table seeded!');
    $this->call('AuthorsTableSeeder');
    $this->command->info('Authors table seeded!');
    **$this->call('BooksTableSeeder');**
 **$this->command->info('Books table seeded!');**
  }

}

现在,我们需要使用以下artisan命令向数据库中添加种子数据:

**php artisan db:seed**

创建和迁移购物车数据库

正如你所知,所有电子商务应用程序都应该有一个购物车。在这个应用程序中,我们也会有一个购物车。我们将设计一个基于会员的购物车,这意味着我们可以存储和显示每个访问者的购物车和购物车商品。因此,我们需要一个Cart模型来存储购物车商品。它将是一个非常简单的结构。让我们在app/models下创建Cart.php文件并添加以下代码:

<?php
Class Cart extends Eloquent {

protected $table = 'carts';

protected $fillable = array('member_id','book_id','amount','total');

public function Books(){

return $this->belongsTo('Book','book_id');

}

}

现在我们需要一个迁移文件来创建carts表。要创建一个迁移文件,输入以下命令:

**php artisan migrate:make create_carts_table --table=carts --create**

打开最近创建并位于app/database/migrations/目录下的迁移文件。我们需要编辑up()函数如下代码片段所示:

  public function up()
  {
    Schema::create('carts', function(Blueprint $table)
    {
      $table->increments('id');
      $table->integer('member_id');
      $table->integer('book_id');
      $table->integer('amount');
      $table->decimal('total', 10, 2);
      $table->timestamps();
    });
  }

要应用迁移,我们需要使用以下artisan命令进行迁移:

**php artisan migrate**

创建和迁移订单数据库

为了存储会员的订单,我们需要两个表。其中之一是orders表,它将存储运输细节、会员 ID 和订单的总价值。另一个是order_books表。这个表将存储订单的书籍,并且将是我们的中间表。在这个模型中,我们将使用belongsToMany()关系。这是因为一个订单可以有多本书。

因此,首先我们需要一个Order模型来存储书籍订单。让我们在app/models下创建Order.php文件,并添加以下代码:

<?php
Class Order extends Eloquent {

protected $table = 'orders';

protected $fillable = array('member_id','address','total');

public function orderItems()
    {
        return $this->belongsToMany('Book') ->withPivot('amount','total');
    }

}

正如您在代码中所看到的,我们使用了一个名为withPivot()的新选项,它与belongsToMany()函数一起使用。通过withPivot()函数,我们可以从我们的中间表中获取额外的字段。通常情况下,如果没有这个函数,关系查询只能通过相关行的id对象从中间表中访问。这对我们的应用程序是必要的,因为价格变动。因此,可能在任何价格变动之前完成的先前订单不受影响。

现在我们需要一个迁移文件来创建carts表。要创建一个migration文件,可以使用以下命令:

**php artisan migrate:make create_orders_table --table=orders --create**

打开最近创建的位于app/database/migrations目录下的迁移文件。我们需要编辑up()函数如下所示:

  public function up()
  {
    Schema::create('orders', function(Blueprint $table)
    {
      $table->increments('id');
      $table->integer('member_id');
      $table->text('address');
      $table->decimal('total', 10, 2);
      $table->timestamps();
    });
  }

要应用迁移,我们需要使用以下artisan命令进行迁移:

**php artisan migrate**

让我们创建我们的pivot表。我们需要一个迁移文件来创建order_books表。要创建一个迁移文件,可以使用以下命令:

**php artisan migrate:make create_order_books_table --table=order_books --create**

打开最近创建的位于app/database/migrations目录下的迁移文件。我们需要编辑up()函数如下所示:

  public function up()
  {
    Schema::create('order_books', function(Blueprint $table)
    {
      $table->increments('id');
      $table->integer('order_id');
      $table->integer('book_id');
      $table->integer('amount');
$table->decimal('price', 10, 2);
      $table->decimal('total', 10, 2);
    });  
}

要应用迁移,我们需要使用以下artisan命令进行迁移:

**php artisan migrate**

我们的数据库设计和模型已经完成。现在我们需要编写控制器和我们应用程序的前端页面。

列出书籍

首先,我们需要列出我们的产品。为此,我们需要创建一个名为BookController的控制器。让我们在app/controllers/下创建一个文件,并将其保存为BookController.php。控制器代码应该如下所示:

<?php
class BookController extends BaseController{

  public function getIndex()
  {

    $books = Book::all();

    return View::make('book_list')->with('books',$books);

  }
}

该代码简单地从我们的books表中获取所有的书籍,并将数据传递给book_list.blade.php模板,使用$books变量。因此,我们需要在app/controllers/下创建一个模板文件,命名为book_list.blade.php。在这之前,我们需要一个用于我们模板的布局页面。使用布局文件可以很好地管理 html 代码。因此,首先我们需要在app/controllers/下创建一个模板文件,命名为main_layout.blade.php。代码应该如下所示:

<!DOCTYPE html>
<html>
<head>
  <title>Awesome Book Store</title>
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <!-- Bootstrap -->
  <link href="//netdna.bootstrapcdn.com/twitter-bootstrap/2.3.1/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="navbar navbar-inverse nav">
    <div class="navbar-inner">
        <div class="container">
            <a class="btn btn-navbar" data-toggle="collapse" data-target=".nav-collapse">
                <span class="icon-bar"></span>
                <span class="icon-bar"></span>
                <span class="icon-bar"></span>
            </a>
            <a class="brand" href="/">Awesome Book Store</a>
            <div class="nav-collapse collapse">
              <ul class="nav">
                  <li class="divider-vertical"></li>
                  <li><a href="/"><i class="icon-home icon-white"></i> Book List</a></li>
              </ul>
              <div class="pull-right">
                <ul class="nav pull-right">
                @if(!Auth::check())
                <ul class="nav pull-right">
                  <li class="divider-vertical"></li>
                  <li class="dropdown">
                    <a class="dropdown-toggle" href="#" data-toggle="dropdown">Sign In <strong class="caret"></strong></a>
                    <div class="dropdown-menu" style="padding: 15px; padding-bottom: 0px;">
                      <p>Please Login</a>
                        <form action="/user/login" method="post" accept-charset="UTF-8">
                          <input id="email" style="margin-bottom: 15px;" type="text" name="email" size="30" placeholder="email" />
                          <input id="password" style="margin-bottom: 15px;" type="password" name="password" size="30" />
                          <input class="btn btn-info" style="clear: left; width: 100%; height: 32px; font-size: 13px;" type="submit" name="commit" value="Sign In" />
                        </form>
                      </div>
                    </li>
                  </ul>
                @else
                <li><a href="/cart"><i class="icon-shopping-cart icon-white"></i> Your Cart</a></li>
                  <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">Welcome, {{Auth::user()->name}} <b class="caret"></b></a>
                        <ul class="dropdown-menu">
                            <li><a href="/user/orders"><i class="icon-envelope"></i> My Orders</a></li>
                            <li class="divider"></li>
                            <li><a href="/user/logout"><i class="icon-off"></i> Logout</a></li>
                        </ul>
                    </li>
                @endif
                </ul>
              </div>
            </div>
        </div>
    </div>
</div>

@yield('content')

  <script src="http://code.jquery.com/jquery.js"></script>
  <script src="//netdna.bootstrapcdn.com/bootstrap/3.0.0/js/bootstrap.min.js"></script>
  <script type="text/javascript">
  $(function() {
    $('.dropdown-toggle').dropdown();

    $('.dropdown input, .dropdown label').click(function(e) {
      e.stopPropagation();
    });
  });

  @if(isset($error))
    alert("{{$error}}");
  @endif

  @if(Session::has('error'))
    alert("{{Session::get('error')}}");
  @endif

  @if(Session::has('message'))
    alert("{{Session::get('message')}}");
  @endif

  </script>
</body>
</html>

模板文件包含一个菜单、一个登录表单和一些用于下拉式登录表单的 JavaScript 代码。我们将使用该文件作为我们应用程序的布局模板。我们需要在app/controllers目录下的UserController.php文件中编写我们的登录和注销函数。登录函数应该如下所示的代码:

<?php
class UserController extends BaseController {

  public function postLogin()
  {
    $email=Input::get('email');
    $password=Input::get('password');

    if (Auth::attempt(array('email' => $email, 'password' => $password)))
    {
        return Redirect::route('index');

    }else{

      return Redirect::route('index')
        ->with('error','Please check your password & email');
    }
  }

  public function getLogout()
  {
    Auth::logout();
    return Redirect::route('index');
  }
}

如下所示,我们需要在我们的路由文件routes.php中添加路由:

Route::get('/', array('as'=>'index','uses'=>'BookController@getIndex'));
Route::post('/user/login', array('uses'=>'UserController@postLogin'));
Route::get('/user/logout', array('uses'=>'UserController@getLogout'));

创建一个模板文件来列出书籍

现在我们需要一个模板文件来列出书籍。如前所述,我们需要在app/views/下创建一个模板文件,并将其保存为book_list.blade.php。该文件应该如下所示:

@extends('main_layout')

@section('content')

<div class="container">
  <div class="span12">
    <div class="row">
      <ul class="thumbnails">
        @foreach($books as $book)
        <li class="span4">
          <div class="thumbnail">
            <img src="/images/{{$book->cover}}" alt="ALT NAME">
            <div class="caption">
              <h3>{{$book->title}}</h3>
              <p>Author : <b>{{$book->author->name}} {{$book->author->surname}}</b></p>
              <p>Price : <b>{{$book->price}}</b></p>
              <form action="/cart/add" name="add_to_cart" method="post" accept-charset="UTF-8">
                <input type="hidden" name="book" value="{{$book->id}}" />
                <select name="amount" style="width: 100%;">
                  <option value="1">1</option>
                  <option value="2">2</option>
                  <option value="3">3</option>
                  <option value="4">4</option>
                  <option value="5">5</option>
                </select>
              <p align="center"><button class="btn btn-info btn-block">Add to Cart</button></p>
            </form>
            </div>
          </div>
        </li>
        @endforeach
      </ul>
    </div>
  </div>
</div>

@stop

模板文件中有一个表单,用于将书籍添加到购物车中。现在我们需要在app/controllers/目录下的CartController.php文件中编写我们的函数。CartController.php的内容应该如下所示:

<?php
class CartController extends BaseController {

  public function postAddToCart()
  {
    $rules=array(

      'amount'=>'required|numeric',
      'book'=>'required|numeric|exists:books,id'
    );

    $validator = Validator::make(Input::all(), $rules);

      if ($validator->fails())
      {
          return Redirect::route('index')->with('error','The book could not added to your cart!');
      }

      $member_id = Auth::user()->id;
      $book_id = Input::get('book');
      $amount = Input::get('amount');

      $book = Book::find($book_id);
      $total = $amount*$book->price;

       $count = Cart::where('book_id','=',$book_id)->where('member_id','=',$member_id)->count();

       if($count){

         return Redirect::route('index')->with('error','The book already in your cart.');
       }

      Cart::create(
        array(
        'member_id'=>$member_id,
        'book_id'=>$book_id,
        'amount'=>$amount,
        'total'=>$total
        ));

      return Redirect::route('cart');
  }

  public function getIndex(){

    $member_id = Auth::user()->id;

    $cart_books=Cart::with('Books')->where('member_id','=',$member_id)->get();

    $cart_total=Cart::with('Books')->where('member_id','=',$member_id)->sum('total');

    if(!$cart_books){

      return Redirect::route('index')->with('error','Your cart is empty');
    }

    return View::make('cart')
          ->with('cart_books', $cart_books)
          ->with('cart_total',$cart_total);
  }

  public function getDelete($id){

    $cart = Cart::find($id)->delete();

    return Redirect::route('cart');
  }

}

我们的控制器有三个函数。其中之一是postAddToCart()

public function postAddToCart()
  {
    $rules=array(

      'amount'=>'required|numeric',
      'book'=>'required|numeric|exists:books,id'
    );

    $validator = Validator::make(Input::all(), $rules);

      if ($validator->fails())
      {
          return Redirect::route('index')->with('error','The book could not added to your cart!');
      }

      $member_id = Auth::user()->id;
      $book_id = Input::get('book');
      $amount = Input::get('amount');

      $book = Book::find($book_id);
      $total = $amount*$book->price;

       $count = Cart::where('book_id','=',$book_id)->where('member_id','=',$member_id)->count();

       if($count){

         return Redirect::route('index')->with('error','The book already in your cart.');
       }

      Cart::create(
        array(
        'member_id'=>$member_id,
        'book_id'=>$book_id,
        'amount'=>$amount,
        'total'=>$total
        ));

      return Redirect::route('cart');
  }

该函数基本上首先验证了提交的数据。经过验证的数据检查carts表中是否有重复记录。如果会员的购物车中没有相同的书籍,函数将在carts表中创建一个新记录。CartController的第二个函数是getIndex()

  public function getIndex(){

    $member_id = Auth::user()->id;

    $cart_books=Cart::with('Books')->where('member_id','=',$member_id)->get();

    $cart_total=Cart::with('Books')->where('member_id','=',$member_id)->sum('total');

    if(!$cart_books){

      return Redirect::route('index')->with('error','Your cart is empty');
    }

    return View::make('cart')
          ->with('cart_books', $cart_books)
          ->with('cart_total',$cart_total);
  }

该功能获取整个购物车商品、书籍信息和购物车总额,并将数据传递给模板文件。该类的最后一个函数是getDelete()

  public function getDelete($id){

    $cart = Cart::find($id)->delete();

    return Redirect::route('cart');
  }

该功能基本上是从carts表中查找给定 ID 并删除记录。我们使用该功能从购物车中删除商品。现在我们需要创建一个模板文件。该文件将显示会员的所有购物车信息,并包含订单表单。将文件保存在app/views/下,命名为cart.blade.phpcart.blade.php的内容应该如下所示:

@extends('main_layout')

@section('content')

<div class="container" style="width:60%">
  <h1>Your Cart</h1>
  <table class="table">
    <tbody>
      <tr>
        <td>
          <b>Title</b>
        </td>
        <td>
          <b>Amount</b>
        </td>
        <td>
          <b>Price</b>
        </td>
        <td>
          <b>Total</b>
        </td>
        <td>
          <b>Delete</b>
        </td>
      </tr>
      @foreach($cart_books as $cart_item)
        <tr>
          <td>{{$cart_item->Books->title}}</td>
          <td>
           {{$cart_item->amount}}
          </td>
          <td>
            {{$cart_item->Books->price}}
          </td>
          <td>
           {{$cart_item->total}}
          </td>
          <td>
            <a href="{{URL::route('delete_book_from_cart',array($cart_item->id))}}">Delete</a>
          </td>
        </tr>
      @endforeach
      <tr>
        <td>
        </td>
        <td>
        </td>
        <td>
          <b>Total</b>
        </td>
        <td>
          <b>{{$cart_total}}</b>
        </td>
        <td>
        </td>        
      </tr>
    </tbody>
  </table>
  <h1>Shipping</h1>
  <form action="/order" method="post" accept-charset="UTF-8">
    <label>Address</label>
    <textarea class="span4" name="address" rows="5"></textarea>
    <button class="btn btn-block btn-primary btn-large">Place order</button>
  </form>
</div>
@stop

现在我们需要编写我们的路由。控制器的功能应该只对会员可访问。因此,我们可以轻松地使用 Laravel 内置的auth.basic过滤器:

Route::get('/cart', array('before'=>'auth.basic','as'=>'cart','uses'=>'CartController@getIndex'));
Route::post('/cart/add', array('before'=>'auth.basic','uses'=>'CartController@postAddToCart'));
Route::get('/cart/delete/{id}', array('before'=>'auth.basic','as'=>'delete_book_from_cart','uses'=>'CartController@getDelete'));

接受订单

正如您可能记得的,我们已经在位于app/views/cart.blade.php模板文件中创建了一个订单表单。现在我们需要处理订单。让我们在app/controllers/下编写OrderController.php文件:

<?php
class OrderController extends BaseController {

  public function postOrder()
  {
    $rules=array(

      'address'=>'required'
    );

  $validator = Validator::make(Input::all(), $rules);

      if ($validator->fails())
      {
          return Redirect::route('cart')->with('error','Address field is required!');
      }

      $member_id = Auth::user()->id;
      $address = Input::get('address');

       $cart_books = Cart::with('Books')->where('member_id','=',$member_id)->get();

       $cart_total=Cart::with('Books')->where('member_id','=',$member_id)->sum('total');

       if(!$cart_books){

         return Redirect::route('index')->with('error','Your cart is empty.');
       }

      $order = Order::create(
        array(
        'member_id'=>$member_id,
        'address'=>$address,
        'total'=>$cart_total
        ));

      foreach ($cart_books as $order_books) {

        $order->orderItems()->attach($order_books->book_id, array(
          'amount'=>$order_books->amount,
          'price'=>$order_books->Books->price,
          'total'=>$order_books->Books->price*$order_books->amount
          ));

      }

      Cart::where('member_id','=',$member_id)->delete();

      return Redirect::route('index')->with('message','Your order processed successfully.');
  }

  public function getIndex(){

    $member_id = Auth::user()->id;

    if(Auth::user()->admin){

      $orders=Order::all();

    }else{

      $orders=Order::with('orderItems')->where('member_id','=',$member_id)->get();
    }

    if(!$orders){

      return Redirect::route('index')->with('error','There is no order.');
    }

    return View::make('order')
          ->with('orders', $orders);
  }
}

控制器有两个功能。其中之一是postOrder()

public function postOrder()
  {
    $rules=array(

      'address'=>'required'
    );

  $validator = Validator::make(Input::all(), $rules);

      if ($validator->fails())
      {
          return Redirect::route('cart')->with('error','Address field is required!');
      }

      $member_id = Auth::user()->id;
      $address = Input::get('address');

       $cart_books = Cart::with('Books')->where('member_id','=',$member_id)->get();

       $cart_total=Cart::with('Books')->where('member_id','=',$member_id)->sum('total');

       if(!$cart_books){

         return Redirect::route('index')->with('error','Your cart is empty.');
       }

      $order = Order::create(
        array(
        'member_id'=>$member_id,
        'address'=>$address,
        'total'=>$cart_total
        ));

      foreach ($cart_books as $order_books) {

        $order->orderItems()->attach($order_books->book_id, array(
          'amount'=>$order_books->amount,
          'price'=>$order_books->Books->price,
          'total'=>$order_books->Books->price*$order_books->amount
          ));

      }

      Cart::where('member_id','=',$member_id)->delete();

      return Redirect::route('index')->with('message','Your order processed successfully.');
  }

该功能首先验证发布的数据。验证成功后,该功能在orders表上创建一个新订单。order表存储会员 ID、送货地址和订单总额。然后,该功能将所有购物车商品附加到与它们的数量、价格和总额相关的中间表中。通过这种方式,订单商品不会受到任何价格变化的影响。然后,该功能从会员的购物车中删除所有商品。控制器的第二个功能是getIndex()

public function getIndex(){

    $member_id = Auth::user()->id;

    if(Auth::user()->admin){

      $orders=Order::all();

    }else{

      $orders=Order::with('orderItems')->where('member_id','=',$member_id)->get();
    }

    if(!$orders){

      return Redirect::route('index')->with('error','There is no order.');
    }

    return View::make('order')
          ->with('orders', $orders);
  }

该功能通过查看当前用户的权限来查询数据库。如果当前用户具有管理员权限,则该功能获取所有订单。如果当前用户没有管理员权限,则该功能只获取用户的订单。因此,现在我们需要编写我们的路由。将以下路由代码添加到app/routes.php中:

Route::post('/order', array('before'=>'auth.basic','uses'=>'OrderController@postOrder'));
Route::get('/user/orders', array('before'=>'auth.basic','uses'=>'OrderController@getIndex'));

我们的电子商务应用程序几乎完成了。现在我们需要添加一个模板文件。将文件保存在app/views/下,命名为cart.blade.phpcart.blade.php的内容应该如下所示:

@extends('main_layout')
@section('content')
<div class="container" style="width:60%">
<h3>Your Orders</h3>
<div class="menu">
  <div class="accordion">
@foreach($orders as $order)
 <div class="accordion-group">
      <div class="accordion-heading country">
        @if(Auth::user()->admin)
        <a class="accordion-toggle" data-toggle="collapse" href="#order{{$order->id}}">Order #{{$order->id}} - {{$order->User->name}} - {{$order->created_at}}</a>
        @else
        <a class="accordion-toggle" data-toggle="collapse" href="#order{{$order->id}}">Order #{{$order->id}} - {{$order->created_at}}</a>
        @endif
      </div>
      <div id="order{{$order->id}}" class="accordion-body collapse">
        <div class="accordion-inner">
          <table class="table table-striped table-condensed">
            <thead>
              <tr>
              <th>
              Title
              </th>
              <th>
              Amount
              </th>
              <th>
              Price
              </th>
              <th>
              Total
              </th>
              </tr>
            </thead>   
            <tbody>
            @foreach($order->orderItems as $orderitem)
              <tr>
                <td>{{$orderitem->title}}</td>
                <td>{{$orderitem->pivot->amount}}</td>
                <td>{{$orderitem->pivot->price}}</td>
                <td>{{$orderitem->pivot->total}}</td>
              </tr>
            @endforeach
              <tr>
                <td></td>
                <td></td>
                <td><b>Total</b></td>
                <td><b>{{$order->total}}</b></td>
              </tr>
              <tr>
                <td><b>Shipping Address</b></td>
                <td>{{$order->address}}</td>
                <td></td>
                <td></td>
              </tr>
            </tbody>
          </table>
        </div>
      </div>
    </div>
@endforeach
</div>
</div>
@stop

模板文件包含有关订单的所有信息。该模板是如何使用中间表列的一个非常简单的示例。中间数据以数组形式呈现。因此,我们使用foreach循环来使用数据。您可以存储任何您不希望受数据库中任何更改影响的数据,例如价格更改。

总结

在本章中,我们构建了一个简单的电子商务应用程序。正如您所看到的,由于 Laravel 的模板系统和内置授权系统,您可以轻松地创建庞大的应用程序。您可以通过第三方包来改进应用程序。自 Laravel 版本 4 以来,主要的包管理器一直是 Composer。在packagist.org上有一个庞大的库,提供了用于图像处理、社交媒体 API 等的包。包的数量每天都在增加,它们默认情况下都与 Laravel 兼容。我们建议在编写任何代码之前,您先查看 Packagist 网站。在您阅读这些句子的同时,仍有许多贡献者在分享他们的代码。审查其他程序员的代码会为旧问题提供新的答案。不要忘记与他人分享您的知识,以获得更好的编程体验。

在整本书中,我们试图解释如何使用 Laravel PHP 框架构建不同类型的应用程序。因此,我们涵盖了 RESTful 控制器、路由、路由过滤器、认证类、Blade 模板引擎、数据库迁移、数据库种子、字符串和文件处理类。此外,我们还提供了一些关于如何快速开发 Laravel 的技巧。我们希望这本书能成为学习 Laravel 框架的良好资源。

本书的合著者Halil İbrahim Yılmaz开发了一个名为 HERKOBI 的基于 Laravel 的开源多语言 CMS。您可以使用本书章节的源代码和 CMS 的源代码。您可以在herkobi.orgherkobi.com上访问 CRM 和代码。

Laravel 有一个很好的社区,非常乐于助人和友好。您可以在 Laravel 论坛上提出任何问题。国际 Laravel 社区可以在laravel.com上访问。Laravel 还有一些国家的 Laravel 社区,比如土耳其的 Laravel 社区,位于laravel.gen.tr

当您在书中或 Laravel PHP 框架中需要帮助时,可以给作者发送电子邮件。

感谢您对本书感兴趣并购买。

posted @ 2024-05-05 00:11  绝不原创的飞龙  阅读(5)  评论(0编辑  收藏  举报